home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / script_autoit.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-06-21  |  90.2 KB  |  2,256 lines

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // AutoIt
  4. //
  5. // Copyright (C)1999-2006:
  6. //        - Jonathan Bennett <jon@hiddensoft.com>
  7. //        - Others listed at http://www.autoitscript.com/autoit3/docs/credits.htm
  8. //      - Chris Mallett (support@autohotkey.com): various enhancements and
  9. //        adaptation of this file's functions to interface with AutoHotkey.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published by
  13. // the Free Software Foundation; either version 2 of the License, or
  14. // (at your option) any later version.
  15. //
  16. ///////////////////////////////////////////////////////////////////////////////
  17.  
  18. #include "stdafx.h" // pre-compiled headers
  19. #include <winsock.h>  // for WSADATA.  This also requires wsock32.lib to be linked in.
  20. #include <tlhelp32.h> // For the ProcessExist routines.
  21. #include <wininet.h> // For URLDownloadToFile().
  22. #include "script.h"
  23. #include "globaldata.h" // for g_ErrorLevel and probably other globals.
  24. #include "window.h" // For ControlExist().
  25. #include "application.h" // For SLEEP_WITHOUT_INTERRUPTION and MsgSleep().
  26.  
  27.  
  28. ResultType Script::DoRunAs(char *aCommandLine, char *aWorkingDir, bool aDisplayErrors, bool aUpdateLastError, WORD aShowWindow
  29.     , Var *aOutputVar, PROCESS_INFORMATION &aPI, bool &aSuccess // Output parameters we set for caller, but caller must have initialized aSuccess to false.
  30.     , HANDLE &aNewProcess, char *aSystemErrorText)              // Same.  Caller must ensure aSystemErrorText is at least 512 in size.
  31. {
  32.     typedef BOOL (WINAPI *MyCreateProcessWithLogonW)(
  33.         LPCWSTR lpUsername,                 // user's name
  34.         LPCWSTR lpDomain,                   // user's domain
  35.         LPCWSTR lpPassword,                 // user's password
  36.         DWORD dwLogonFlags,                 // logon option
  37.         LPCWSTR lpApplicationName,          // executable module name
  38.         LPWSTR lpCommandLine,               // command-line string
  39.         DWORD dwCreationFlags,              // creation flags
  40.         LPVOID lpEnvironment,               // new environment block
  41.         LPCWSTR lpCurrentDirectory,         // current directory name
  42.         LPSTARTUPINFOW lpStartupInfo,       // startup information
  43.         LPPROCESS_INFORMATION lpProcessInfo // process information
  44.         );
  45.     // Get a handle to the DLL module that contains CreateProcessWithLogonW
  46.     HINSTANCE hinstLib = LoadLibrary("advapi32");
  47.     if (!hinstLib)
  48.     {
  49.         if (aDisplayErrors)
  50.             ScriptError("RunAs: Missing advapi32.dll." ERR_ABORT);
  51.         return FAIL;
  52.     }
  53.     MyCreateProcessWithLogonW lpfnDLLProc = (MyCreateProcessWithLogonW)GetProcAddress(hinstLib, "CreateProcessWithLogonW");
  54.     if (!lpfnDLLProc)
  55.     {
  56.         FreeLibrary(hinstLib);
  57.         if (aDisplayErrors)
  58.             ScriptError("CreateProcessWithLogonW." ERR_ABORT); // Short msg since it probably never happens.
  59.         return FAIL;
  60.     }
  61.     // Set up wide char version that we need for CreateProcessWithLogon
  62.     // init structure for running programs (wide char version)
  63.     STARTUPINFOW wsi = {0};
  64.     wsi.cb            = sizeof(STARTUPINFOW);
  65.     wsi.dwFlags        = STARTF_USESHOWWINDOW;
  66.     wsi.wShowWindow = aShowWindow;
  67.     // The following are left initialized to 0/NULL (initialized earlier above):
  68.     //wsi.lpReserved = NULL;
  69.     //wsi.lpDesktop    = NULL;
  70.     //wsi.lpTitle = NULL;
  71.     //wsi.cbReserved2 = 0;
  72.     //wsi.lpReserved2 = NULL;
  73.  
  74.     // Convert to wide character format:
  75.     WCHAR command_line_wide[LINE_SIZE], working_dir_wide[MAX_PATH];
  76.     ToWideChar(aCommandLine, command_line_wide, LINE_SIZE); // Dest. size is in wchars, not bytes.
  77.     if (aWorkingDir && *aWorkingDir)
  78.         ToWideChar(aWorkingDir, working_dir_wide, MAX_PATH); // Dest. size is in wchars, not bytes.
  79.     else
  80.         *working_dir_wide = 0;  // wide-char terminator.
  81.  
  82.     if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
  83.         , command_line_wide, 0, 0, *working_dir_wide ? working_dir_wide : NULL, &wsi, &aPI))
  84.     {
  85.         aSuccess = true;
  86.         if (aPI.hThread)
  87.             CloseHandle(aPI.hThread); // Required to avoid memory leak.
  88.         aNewProcess = aPI.hProcess;
  89.         if (aOutputVar)
  90.             aOutputVar->Assign(aPI.dwProcessId);
  91.     }
  92.     else
  93.         GetLastErrorText(aSystemErrorText, 512, aUpdateLastError);  // Caller has ensured that aSystemErrorText is at least this size.
  94.     FreeLibrary(hinstLib);
  95.     return OK;
  96. }
  97.  
  98.  
  99.  
  100. VarSizeType BIV_IPAddress(char *aBuf, char *aVarName)
  101. {
  102.     // aaa.bbb.ccc.ddd = 15, but allow room for larger IP's in the future.
  103.     #define IP_ADDRESS_SIZE 32 // The maximum size of any of the strings we return, including terminator.
  104.     if (!aBuf)
  105.         return IP_ADDRESS_SIZE - 1;  // -1 since we're returning the length of the var's contents, not the size.
  106.  
  107.     WSADATA wsadata;
  108.     if (WSAStartup(MAKEWORD(1, 1), &wsadata)) // Failed (it returns 0 on success).
  109.     {
  110.         *aBuf = '\0';
  111.         return 0;
  112.     }
  113.  
  114.     char host_name[256];
  115.     gethostname(host_name, sizeof(host_name));
  116.     HOSTENT *lpHost = gethostbyname(host_name);
  117.  
  118.     // au3: How many adapters have we?
  119.     int adapter_count = 0;
  120.     while (lpHost->h_addr_list[adapter_count])
  121.         ++adapter_count;
  122.  
  123.     int adapter_index = aVarName[11] - '1'; // A_IPAddress[1-4]
  124.     if (adapter_index >= adapter_count)
  125.         strcpy(aBuf, "0.0.0.0");
  126.     else
  127.     {
  128.         IN_ADDR inaddr;
  129.         memcpy(&inaddr, lpHost->h_addr_list[adapter_index], 4);
  130.         strlcpy(aBuf, (char *)inet_ntoa(inaddr), IP_ADDRESS_SIZE);
  131.     }
  132.  
  133.     WSACleanup();
  134.     return (VarSizeType)strlen(aBuf);
  135. }
  136.  
  137.  
  138.  
  139. VarSizeType BIV_IsAdmin(char *aBuf, char *aVarName)
  140. {
  141.     if (!aBuf)
  142.         return 1;  // The length of the string "1" or "0".
  143.     char result = '0';  // Default.
  144.     if (g_os.IsWin9x())
  145.         result = '1';
  146.     else
  147.     {
  148.         SC_HANDLE h = OpenSCManager(NULL, NULL, SC_MANAGER_LOCK);
  149.         if (h)
  150.         {
  151.             SC_LOCK lock = LockServiceDatabase(h);
  152.             if (lock)
  153.             {
  154.                 UnlockServiceDatabase(lock);
  155.                 result = '1'; // Current user is admin.
  156.             }
  157.             else
  158.             {
  159.                 DWORD lastErr = GetLastError();
  160.                 if (lastErr == ERROR_SERVICE_DATABASE_LOCKED)
  161.                     result = '1'; // Current user is admin.
  162.             }
  163.             CloseServiceHandle(h);
  164.         }
  165.     }
  166.     aBuf[0] = result;
  167.     aBuf[1] = '\0';
  168.     return 1; // Length of aBuf.
  169. }
  170.  
  171.  
  172.  
  173. ResultType Line::PixelGetColor(int aX, int aY, char *aOptions)
  174. {
  175.     if (strcasestr(aOptions, "Slow")) // New mode for v1.0.43.10.  Takes precedence over Alt mode.
  176.         return PixelSearch(aX, aY, aX, aY, 0, 0, aOptions, true); // It takes care of setting ErrorLevel and the output-var.
  177.     Var &output_var = *OUTPUT_VAR;
  178.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  179.     output_var.Assign(); // Init to empty string regardless of whether we succeed here.
  180.  
  181.     if (!(g.CoordMode & COORD_MODE_PIXEL)) // Using relative vs. screen coordinates.
  182.     {
  183.         // Convert from relative to absolute (screen) coordinates:
  184.         RECT rect;
  185.         if (!GetWindowRect(GetForegroundWindow(), &rect))
  186.             return OK;  // Let ErrorLevel tell the story.
  187.         aX += rect.left;
  188.         aY += rect.top;
  189.     }
  190.  
  191.     bool use_alt_mode = strcasestr(aOptions, "Alt") != NULL; // New mode for v1.0.43.10: Two users reported that CreateDC works better in certain windows such as SciTE, at least one some systems.
  192.     HDC hdc = use_alt_mode ? CreateDC("DISPLAY", NULL, NULL, NULL) : GetDC(NULL);
  193.     if (!hdc)
  194.         return OK;  // Let ErrorLevel tell the story.
  195.  
  196.     // Assign the value as an 32-bit int to match Window Spy reports color values.
  197.     // Update for v1.0.21: Assigning in hex format seems much better, since it's easy to
  198.     // look at a hex BGR value to get some idea of the hue.  In addition, the result
  199.     // is zero padded to make it easier to convert to RGB and more consistent in
  200.     // appearance:
  201.     COLORREF color = GetPixel(hdc, aX, aY);
  202.     if (use_alt_mode)
  203.         DeleteDC(hdc);
  204.     else
  205.         ReleaseDC(NULL, hdc);
  206.  
  207.     char buf[32];
  208.     sprintf(buf, "0x%06X", strcasestr(aOptions, "RGB") ? bgr_to_rgb(color) : color);
  209.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  210.     return output_var.Assign(buf);
  211. }
  212.  
  213.  
  214.  
  215. ResultType Line::SplashTextOn(int aWidth, int aHeight, char *aTitle, char *aText)
  216. {
  217.     // Add some caption and frame size to window:
  218.     aWidth += GetSystemMetrics(SM_CXFIXEDFRAME) * 2;
  219.     int min_height = GetSystemMetrics(SM_CYCAPTION) + (GetSystemMetrics(SM_CXFIXEDFRAME) * 2);
  220.     if (g_script.mIsAutoIt2)
  221.     {
  222.         // I think this is probably something like how AutoIt2 does things:
  223.         if (aHeight < min_height)
  224.             aHeight = min_height;
  225.     }
  226.     else // Use the new method that seems more friendly.
  227.         aHeight += min_height;
  228.  
  229.     POINT pt = CenterWindow(aWidth, aHeight); // Determine how to center the window in the region that excludes the task bar.
  230.  
  231.     // My: Probably not too much overhead to do this, though it probably would perform better to resize and
  232.     // "re-text" the existing window rather than recreating it like this:
  233.     DESTROY_SPLASH
  234.  
  235.     // Doesn't seem necessary to have it owned by the main window, but neither
  236.     // does doing so seem to cause any harm.  Feels safer to have it be
  237.     // an independent window.  Update: Must make it owned by the parent window
  238.     // otherwise it will get its own task-bar icon, which is usually undesirable.
  239.     // In addition, making it an owned window should automatically cause it to be
  240.     // destroyed when it's parent window is destroyed:
  241.     g_hWndSplash = CreateWindowEx(WS_EX_TOPMOST, WINDOW_CLASS_SPLASH, aTitle, WS_DISABLED|WS_POPUP|WS_CAPTION
  242.         , pt.x, pt.y, aWidth, aHeight, g_hWnd, (HMENU)NULL, g_hInstance, NULL);
  243.  
  244.     RECT rect;
  245.     GetClientRect(g_hWndSplash, &rect);    // get the client size
  246.  
  247.     // CREATE static label full size of client area.
  248.     HWND static_win = CreateWindowEx(0, "static", aText, WS_CHILD|WS_VISIBLE|SS_CENTER
  249.         , 0, 0, rect.right - rect.left, rect.bottom - rect.top, g_hWndSplash, (HMENU)NULL, g_hInstance, NULL);
  250.  
  251.     if (!g_hFontSplash)
  252.     {
  253.         char default_font_name[65];
  254.         int CyPixels, nSize = 12, nWeight = FW_NORMAL;
  255.         HDC hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
  256.         SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT));        // Get Default Font Name
  257.         GetTextFace(hdc, sizeof(default_font_name) - 1, default_font_name); // -1 just in case, like AutoIt3.
  258.         CyPixels = GetDeviceCaps(hdc, LOGPIXELSY);            // For Some Font Size Math
  259.         DeleteDC(hdc);
  260.         //strcpy(default_font_name,vParams[7].szValue());    // Font Name
  261.         //nSize = vParams[8].nValue();        // Font Size
  262.         //if ( vParams[9].nValue() >= 0 && vParams[9].nValue() <= 1000 )
  263.         //    nWeight = vParams[9].nValue();            // Font Weight
  264.         g_hFontSplash = CreateFont(0-(nSize*CyPixels)/72,0,0,0,nWeight,0,0,0,DEFAULT_CHARSET,
  265.             OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FF_DONTCARE,default_font_name);    // Create Font
  266.         // The font is deleted when by g_script's destructor.
  267.     }
  268.  
  269.     SendMessage(static_win, WM_SETFONT, (WPARAM)g_hFontSplash, MAKELPARAM(TRUE, 0));    // Do Font
  270.     ShowWindow(g_hWndSplash, SW_SHOWNOACTIVATE);                // Show the Splash
  271.     // Doesn't help with the brief delay in updating the window that happens when
  272.     // something like URLDownloadToFile is used immediately after SplashTextOn:
  273.     //InvalidateRect(g_hWndSplash, NULL, TRUE);
  274.     // But this does, but for now it seems unnecessary since the user can always do
  275.     // a manual sleep in the extremely rare cases this ever happens (even when it does
  276.     // happen, the window updates eventually, after the download starts, at least on
  277.     // my system.  Update: Might as well do it since it's a little nicer this way
  278.     // (the text appears more quickly when the command after the splash is something
  279.     // that might keep our thread tied up and unable to check messages).
  280.     SLEEP_WITHOUT_INTERRUPTION(-1)
  281.     // UpdateWindow() would probably achieve the same effect as the above, but it feels safer to do
  282.     // the above because it ensures that our message queue is empty prior to returning to our caller.
  283.     return OK;
  284. }
  285.  
  286.  
  287.  
  288. ResultType Line::WinMenuSelectItem(char *aTitle, char *aText, char *aMenu1, char *aMenu2
  289.     , char *aMenu3, char *aMenu4, char *aMenu5, char *aMenu6, char *aMenu7
  290.     , char *aExcludeTitle, char *aExcludeText)
  291. {
  292.     // Set up a temporary array make it easier to traverse nested menus & submenus
  293.     // in a loop.  Also add a NULL at the end to simplify the loop a little:
  294.     char *menu_param[] = {aMenu1, aMenu2, aMenu3, aMenu4, aMenu5, aMenu6, aMenu7, NULL};
  295.  
  296.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  297.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  298.     if (!target_window)
  299.         return OK;  // Let ErrorLevel tell the story.
  300.  
  301.     HMENU hMenu = GetMenu(target_window);
  302.     if (!hMenu) // Window has no menu bar.
  303.         return OK;  // Let ErrorLevel tell the story.
  304.  
  305.     int menu_item_count = GetMenuItemCount(hMenu);
  306.     if (menu_item_count < 1) // Menu bar has no menus.
  307.         return OK;  // Let ErrorLevel tell the story.
  308.     
  309. #define MENU_ITEM_IS_SUBMENU 0xFFFFFFFF
  310. #define UPDATE_MENU_VARS(menu_pos) \
  311. menu_id = GetMenuItemID(hMenu, menu_pos);\
  312. if (menu_id == MENU_ITEM_IS_SUBMENU)\
  313.     menu_item_count = GetMenuItemCount(hMenu = GetSubMenu(hMenu, menu_pos));\
  314. else\
  315. {\
  316.     menu_item_count = 0;\
  317.     hMenu = NULL;\
  318. }
  319.  
  320.     UINT menu_id = MENU_ITEM_IS_SUBMENU;
  321.     char menu_text[1024];
  322.     bool match_found;
  323.     size_t this_menu_param_length, menu_text_length;
  324.     int pos, target_menu_pos;
  325.     char *this_menu_param;
  326.  
  327.     for (int i = 0; ; ++i)
  328.     {
  329.         this_menu_param = menu_param[i]; // For performance and convenience.
  330.         if (!(this_menu_param && *this_menu_param))
  331.             break;
  332.         if (!hMenu)  // The nesting of submenus ended prior to the end of the list of menu search terms.
  333.             return OK;  // Let ErrorLevel tell the story.
  334.  
  335.         this_menu_param_length = strlen(this_menu_param);
  336.         target_menu_pos = (this_menu_param[this_menu_param_length - 1] == '&') ? ATOI(this_menu_param) - 1 : -1;
  337.         if (target_menu_pos > -1)
  338.         {
  339.             if (target_menu_pos >= menu_item_count)  // Invalid menu position (doesn't exist).
  340.                 return OK;  // Let ErrorLevel tell the story.
  341.             UPDATE_MENU_VARS(target_menu_pos)
  342.         }
  343.         else // Searching by text rather than numerical position.
  344.         {
  345.             for (match_found = false, pos = 0; pos < menu_item_count; ++pos)
  346.             {
  347.                 menu_text_length = GetMenuString(hMenu, pos, menu_text, sizeof(menu_text) - 1, MF_BYPOSITION);
  348.                 // v1.0.43.03: It's debatable, but it seems best to support locale's case insensitivity for
  349.                 // menu items, since menu names tend to adapt to the user's locale.  By contrast, things
  350.                 // like process names (in the Process command) do not tend to change, so it seems best to
  351.                 // have them continue to use stricmp(): 1) avoids breaking exisitng scripts; 2) provides
  352.                 // consistent behavior across multiple locales; 3) performance.
  353.                 match_found = !lstrcmpni(menu_text  // This call is basically a strnicmp() that obeys locale.
  354.                     , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  355.                     , this_menu_param, this_menu_param_length);
  356.                 //match_found = strcasestr(menu_text, this_menu_param);
  357.                 if (!match_found)
  358.                 {
  359.                     // Try again to find a match, this time without the ampersands used to indicate
  360.                     // a menu item's shortcut key:
  361.                     StrReplace(menu_text, "&", "", SCS_SENSITIVE);
  362.                     menu_text_length = strlen(menu_text);
  363.                     match_found = !lstrcmpni(menu_text  // This call is basically a strnicmp() that obeys locale.
  364.                         , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  365.                         , this_menu_param, this_menu_param_length);
  366.                     //match_found = strcasestr(menu_text, this_menu_param);
  367.                 }
  368.                 if (match_found)
  369.                 {
  370.                     UPDATE_MENU_VARS(pos)
  371.                     break;
  372.                 }
  373.             } // inner for()
  374.             if (!match_found) // The search hierarchy (nested menus) specified in the params could not be found.
  375.                 return OK;  // Let ErrorLevel tell the story.
  376.         } // else
  377.     } // outer for()
  378.  
  379.     // This would happen if the outer loop above had zero iterations due to aMenu1 being NULL or blank,
  380.     // or if the caller specified a submenu as the target (which doesn't seem valid since an app would
  381.     // next expect to ever receive a message for a submenu?):
  382.     if (menu_id == MENU_ITEM_IS_SUBMENU)
  383.         return OK;  // Let ErrorLevel tell the story.
  384.  
  385.     // Since the above didn't return, the specified search hierarchy was completely found.
  386.     PostMessage(target_window, WM_COMMAND, (WPARAM)menu_id, 0);
  387.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  388. }
  389.  
  390.  
  391.  
  392. ResultType Line::Control(char *aCmd, char *aValue, char *aControl, char *aTitle, char *aText
  393.     , char *aExcludeTitle, char *aExcludeText)
  394. // ATTACH_THREAD_INPUT has been tested to see if they help any of these work with controls
  395. // in MSIE (whose Internet Explorer_TridentCmboBx2 does not respond to "Control Choose" but
  396. // does respond to "Control Focus").  But it didn't help.
  397. {
  398.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Set default since there are many points of return.
  399.     ControlCmds control_cmd = ConvertControlCmd(aCmd);
  400.     // Since command names are validated at load-time, this only happens if the command name
  401.     // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  402.     // and return:
  403.     if (control_cmd == CONTROL_CMD_INVALID)
  404.         return OK;  // Let ErrorLevel tell the story.
  405.  
  406.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  407.     if (!target_window)
  408.         return OK;  // Let ErrorLevel tell the story.
  409.     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  410.     if (!control_window)
  411.         return OK;  // Let ErrorLevel tell the story.
  412.  
  413.     HWND immediate_parent;  // Possibly not the same as target_window since controls can themselves have children.
  414.     int control_id, control_index;
  415.     DWORD dwResult, new_button_state;
  416.     UINT msg, x_msg, y_msg;
  417.     RECT rect;
  418.     LPARAM lparam;
  419.     vk_type vk;
  420.     int key_count;
  421.     char temp_buf[32];
  422.  
  423.     switch(control_cmd)
  424.     {
  425.     case CONTROL_CMD_CHECK: // au3: Must be a Button
  426.     case CONTROL_CMD_UNCHECK:
  427.     { // Need braces for ATTACH_THREAD_INPUT macro.
  428.         new_button_state = (control_cmd == CONTROL_CMD_CHECK) ? BST_CHECKED : BST_UNCHECKED;
  429.         if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  430.             return OK;  // Let ErrorLevel tell the story.
  431.         if (dwResult == new_button_state) // It's already in the right state, so don't press it.
  432.             break;
  433.         // MSDN docs for BM_CLICK (and au3 author says it applies to this situation also):
  434.         // "If the button is in a dialog box and the dialog box is not active, the BM_CLICK message
  435.         // might fail. To ensure success in this situation, call the SetActiveWindow function to activate
  436.         // the dialog box before sending the BM_CLICK message to the button."
  437.         ATTACH_THREAD_INPUT
  438.         SetActiveWindow(target_window == control_window ? GetNonChildParent(control_window) : target_window); // v1.0.44.13: Fixed to allow for the fact that target_window might be the control itself (e.g. via ahk_id %ControlHWND%).
  439.         if (!GetWindowRect(control_window, &rect))    // au3: Code to primary click the centre of the control
  440.             rect.bottom = rect.left = rect.right = rect.top = 0;
  441.         lparam = MAKELPARAM((rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2);
  442.         PostMessage(control_window, WM_LBUTTONDOWN, MK_LBUTTON, lparam);
  443.         PostMessage(control_window, WM_LBUTTONUP, 0, lparam);
  444.         DETACH_THREAD_INPUT
  445.         break;
  446.     }
  447.  
  448.     case CONTROL_CMD_ENABLE:
  449.         EnableWindow(control_window, TRUE);
  450.         break;
  451.  
  452.     case CONTROL_CMD_DISABLE:
  453.         EnableWindow(control_window, FALSE);
  454.         break;
  455.  
  456.     case CONTROL_CMD_SHOW:
  457.         ShowWindow(control_window, SW_SHOWNOACTIVATE); // SW_SHOWNOACTIVATE has been seen in some example code for this purpose.
  458.         break;
  459.  
  460.     case CONTROL_CMD_HIDE:
  461.         ShowWindow(control_window, SW_HIDE);
  462.         break;
  463.  
  464.     case CONTROL_CMD_STYLE:
  465.     case CONTROL_CMD_EXSTYLE:
  466.     {
  467.         if (!*aValue)
  468.             return OK; // Seems best not to treat an explicit blank as zero.  Let ErrorLevel tell the story. 
  469.         int style_index = (control_cmd == CONTROL_CMD_STYLE) ? GWL_STYLE : GWL_EXSTYLE;
  470.         DWORD new_style, orig_style = GetWindowLong(control_window, style_index);
  471.         // +/-/^ are used instead of |&^ because the latter is confusing, namely that & really means &=~style, etc.
  472.         if (!strchr("+-^", *aValue))  // | and & are used instead of +/- to allow +/- to have their native function.
  473.             new_style = ATOU(aValue); // No prefix, so this new style will entirely replace the current style.
  474.         else
  475.         {
  476.             ++aValue; // Won't work combined with next line, due to next line being a macro that uses the arg twice.
  477.             DWORD style_change = ATOU(aValue);
  478.             switch(aValue[-1])
  479.             {
  480.             case '+': new_style = orig_style | style_change; break;
  481.             case '-': new_style = orig_style & ~style_change; break;
  482.             case '^': new_style = orig_style ^ style_change; break;
  483.             }
  484.         }
  485.         if (new_style == orig_style) // v1.0.45.04: Ask for an unnecessary change (i.e. one that is already in effect) should not be considered an error.
  486.             return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  487.         // Currently, BM_SETSTYLE is not done when GetClassName() says that the control is a button/checkbox/groupbox.
  488.         // This is because the docs for BM_SETSTYLE don't contain much, if anything, that anyone would ever
  489.         // want to change.
  490.         SetLastError(0); // Prior to SetWindowLong(), as recommended by MSDN.
  491.         if (SetWindowLong(control_window, style_index, new_style) || !GetLastError()) // This is the precise way to detect success according to MSDN.
  492.         {
  493.             // Even if it indicated success, sometimes it failed anyway.  Find out for sure:
  494.             if (GetWindowLong(control_window, style_index) != orig_style) // Even a partial change counts as a success.
  495.             {
  496.                 InvalidateRect(control_window, NULL, TRUE); // Quite a few styles require this to become visibly manifest.
  497.                 return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  498.             }
  499.         }
  500.         return OK; // Let ErrorLevel tell the story. As documented, DoControlDelay is not done for these.
  501.     }
  502.  
  503.     case CONTROL_CMD_SHOWDROPDOWN:
  504.     case CONTROL_CMD_HIDEDROPDOWN:
  505.         // CB_SHOWDROPDOWN: Although the return value (dwResult) is always TRUE, SendMessageTimeout()
  506.         // will return failure if it times out:
  507.         if (!SendMessageTimeout(control_window, CB_SHOWDROPDOWN
  508.             , (WPARAM)(control_cmd == CONTROL_CMD_SHOWDROPDOWN ? TRUE : FALSE)
  509.             , 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  510.             return OK;  // Let ErrorLevel tell the story.
  511.         break;
  512.  
  513.     case CONTROL_CMD_TABLEFT:
  514.     case CONTROL_CMD_TABRIGHT: // must be a Tab Control
  515.         key_count = *aValue ? ATOI(aValue) : 1;
  516.         vk = (control_cmd == CONTROL_CMD_TABLEFT) ? VK_LEFT : VK_RIGHT;
  517.         lparam = (LPARAM)(vk_to_sc(vk) << 16);
  518.         for (int i = 0; i < key_count; ++i)
  519.         {
  520.             // DoControlDelay isn't done for every iteration because it seems likely that
  521.             // the Sleep(0) will take care of things.
  522.             PostMessage(control_window, WM_KEYDOWN, vk, lparam | 0x00000001);
  523.             SLEEP_WITHOUT_INTERRUPTION(0); // Au3 uses a Sleep(0).
  524.             PostMessage(control_window, WM_KEYUP, vk, lparam | 0xC0000001);
  525.         }
  526.         break;
  527.  
  528.     case CONTROL_CMD_ADD:
  529.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  530.         {
  531.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  532.             aControl = temp_buf;
  533.         }
  534.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. !strnicmp for TListBox/TComboBox.
  535.             msg = CB_ADDSTRING;
  536.         else if (strcasestr(aControl, "List"))
  537.             msg = LB_ADDSTRING;
  538.         else
  539.             return OK;  // Must be ComboBox or ListBox.  Let ErrorLevel tell the story.
  540.         if (!SendMessageTimeout(control_window, msg, 0, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult))
  541.             return OK;  // Let ErrorLevel tell the story.
  542.         if (dwResult == CB_ERR || dwResult == CB_ERRSPACE) // General error or insufficient space to store it.
  543.             // CB_ERR == LB_ERR
  544.             return OK;  // Let ErrorLevel tell the story.
  545.         break;
  546.  
  547.     case CONTROL_CMD_DELETE:
  548.         if (!*aValue)
  549.             return OK;
  550.         control_index = ATOI(aValue) - 1;
  551.         if (control_index < 0)
  552.             return OK;
  553.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  554.         {
  555.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  556.             aControl = temp_buf;
  557.         }
  558.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  559.             msg = CB_DELETESTRING;
  560.         else if (strcasestr(aControl, "List"))
  561.             msg = LB_DELETESTRING;
  562.         else
  563.             return OK;  // Must be ComboBox or ListBox.  Let ErrorLevel tell the story.
  564.         if (!SendMessageTimeout(control_window, msg, (WPARAM)control_index, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  565.             return OK;  // Let ErrorLevel tell the story.
  566.         if (dwResult == CB_ERR)  // CB_ERR == LB_ERR
  567.             return OK;  // Let ErrorLevel tell the story.
  568.         break;
  569.  
  570.     case CONTROL_CMD_CHOOSE:
  571.         if (!*aValue)
  572.             return OK;
  573.         control_index = ATOI(aValue) - 1;
  574.         if (control_index < 0)
  575.             return OK;  // Let ErrorLevel tell the story.
  576.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  577.         {
  578.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  579.             aControl = temp_buf;
  580.         }
  581.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  582.         {
  583.             msg = CB_SETCURSEL;
  584.             x_msg = CBN_SELCHANGE;
  585.             y_msg = CBN_SELENDOK;
  586.         }
  587.         else if (strcasestr(aControl, "List"))
  588.         {
  589.             if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  590.                 msg = LB_SETSEL;
  591.             else // single-select listbox
  592.                 msg = LB_SETCURSEL;
  593.             x_msg = LBN_SELCHANGE;
  594.             y_msg = LBN_DBLCLK;
  595.         }
  596.         else
  597.             return OK;  // Must be ComboBox or ListBox.  Let ErrorLevel tell the story.
  598.         if (msg == LB_SETSEL) // Multi-select, so use the cumulative method.
  599.         {
  600.             if (!SendMessageTimeout(control_window, msg, TRUE, control_index, SMTO_ABORTIFHUNG, 2000, &dwResult))
  601.                 return OK;  // Let ErrorLevel tell the story.
  602.         }
  603.         else // ComboBox or single-select ListBox.
  604.             if (!SendMessageTimeout(control_window, msg, control_index, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  605.                 return OK;  // Let ErrorLevel tell the story.
  606.         if (dwResult == CB_ERR)  // CB_ERR == LB_ERR
  607.             return OK;
  608.         if (   !(immediate_parent = GetParent(control_window))   )
  609.             return OK;
  610.         if (   !(control_id = GetDlgCtrlID(control_window))   )
  611.             return OK;
  612.         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg)
  613.             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  614.             return OK;
  615.         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg)
  616.             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  617.             return OK;
  618.         // Otherwise break and do the end-function processing.
  619.         break;
  620.  
  621.     case CONTROL_CMD_CHOOSESTRING:
  622.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  623.         {
  624.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  625.             aControl = temp_buf;
  626.         }
  627.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  628.         {
  629.             msg = CB_SELECTSTRING;
  630.             x_msg = CBN_SELCHANGE;
  631.             y_msg = CBN_SELENDOK;
  632.         }
  633.         else if (strcasestr(aControl, "List"))
  634.         {
  635.             if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  636.                 msg = LB_FINDSTRING;
  637.             else // single-select listbox
  638.                 msg = LB_SELECTSTRING;
  639.             x_msg = LBN_SELCHANGE;
  640.             y_msg = LBN_DBLCLK;
  641.         }
  642.         else
  643.             return OK;  // Must be ComboBox or ListBox.  Let ErrorLevel tell the story.
  644.         if (msg == LB_FINDSTRING) // Multi-select ListBox (LB_SELECTSTRING is not supported by these).
  645.         {
  646.             DWORD item_index;
  647.             if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &item_index)
  648.                 || item_index == LB_ERR
  649.                 || !SendMessageTimeout(control_window, LB_SETSEL, TRUE, item_index, SMTO_ABORTIFHUNG, 2000, &dwResult)
  650.                 || dwResult == LB_ERR) // Relies on short-circuit boolean.
  651.                 return OK;  // Let ErrorLevel tell the story.
  652.         }
  653.         else // ComboBox or single-select ListBox.
  654.             if (!SendMessageTimeout(control_window, msg, 1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult)
  655.                 || dwResult == CB_ERR) // CB_ERR == LB_ERR
  656.                 return OK;  // Let ErrorLevel tell the story.
  657.         if (   !(immediate_parent = GetParent(control_window))   )
  658.             return OK;
  659.         if (   !(control_id = GetDlgCtrlID(control_window))   )
  660.             return OK;
  661.         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg)
  662.             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  663.             return OK;
  664.         if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg)
  665.             , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  666.             return OK;
  667.         // Otherwise break and do the end-function processing.
  668.         break;
  669.  
  670.     case CONTROL_CMD_EDITPASTE:
  671.         if (!SendMessageTimeout(control_window, EM_REPLACESEL, TRUE, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult))
  672.             return OK;  // Let ErrorLevel tell the story.
  673.         // Note: dwResult is not used by EM_REPLACESEL since it doesn't return a value.
  674.         break;
  675.     } // switch()
  676.  
  677.     DoControlDelay;  // Seems safest to do this for all of these commands.
  678.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  679. }
  680.  
  681.  
  682.  
  683. ResultType Line::ControlGet(char *aCmd, char *aValue, char *aControl, char *aTitle, char *aText
  684.     , char *aExcludeTitle, char *aExcludeText)
  685. {
  686.     Var &output_var = *OUTPUT_VAR;
  687.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Set default since there are many points of return.
  688.     ControlGetCmds control_cmd = ConvertControlGetCmd(aCmd);
  689.     // Since command names are validated at load-time, this only happens if the command name
  690.     // was contained in a variable reference.  Since that is very rare, just set ErrorLevel
  691.     // and return:
  692.     if (control_cmd == CONTROLGET_CMD_INVALID)
  693.         return output_var.Assign();  // Let ErrorLevel tell the story.
  694.  
  695.     HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
  696.     if (!target_window)
  697.         return output_var.Assign();  // Let ErrorLevel tell the story.
  698.     HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
  699.     if (!control_window)
  700.         return output_var.Assign();  // Let ErrorLevel tell the story.
  701.  
  702.     DWORD dwResult, index, length, item_length, start, end, u, item_count;
  703.     UINT msg, x_msg, y_msg;
  704.     int control_index;
  705.     char *cp, *dyn_buf, temp_buf[32];
  706.  
  707.     switch(control_cmd)
  708.     {
  709.     case CONTROLGET_CMD_CHECKED: //Must be a Button
  710.         if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  711.             return output_var.Assign();
  712.         output_var.Assign(dwResult == BST_CHECKED ? "1" : "0");
  713.         break;
  714.  
  715.     case CONTROLGET_CMD_ENABLED:
  716.         output_var.Assign(IsWindowEnabled(control_window) ? "1" : "0");
  717.         break;
  718.  
  719.     case CONTROLGET_CMD_VISIBLE:
  720.         output_var.Assign(IsWindowVisible(control_window) ? "1" : "0");
  721.         break;
  722.  
  723.     case CONTROLGET_CMD_TAB: // must be a Tab Control
  724.         if (!SendMessageTimeout(control_window, TCM_GETCURSEL, 0, 0, SMTO_ABORTIFHUNG, 2000, &index) || index == -1) // Relies on short-circuit boolean order.
  725.             return output_var.Assign();
  726.         output_var.Assign(index + 1);
  727.         break;
  728.  
  729.     case CONTROLGET_CMD_FINDSTRING:
  730.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  731.         {
  732.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  733.             aControl = temp_buf;
  734.         }
  735.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  736.             msg = CB_FINDSTRINGEXACT;
  737.         else if (strcasestr(aControl, "List"))
  738.             msg = LB_FINDSTRINGEXACT;
  739.         else // Must be ComboBox or ListBox
  740.             return output_var.Assign();  // Let ErrorLevel tell the story.
  741.         if (!SendMessageTimeout(control_window, msg, 1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &index)
  742.             || index == CB_ERR) // CB_ERR == LB_ERR
  743.             return output_var.Assign();
  744.         output_var.Assign(index + 1);
  745.         break;
  746.  
  747.     case CONTROLGET_CMD_CHOICE:
  748.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  749.         {
  750.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  751.             aControl = temp_buf;
  752.         }
  753.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  754.         {
  755.             msg = CB_GETCURSEL;
  756.             x_msg = CB_GETLBTEXTLEN;
  757.             y_msg = CB_GETLBTEXT;
  758.         }
  759.         else if (strcasestr(aControl, "List"))
  760.         {
  761.             msg = LB_GETCURSEL;
  762.             x_msg = LB_GETTEXTLEN;
  763.             y_msg = LB_GETTEXT;
  764.         }
  765.         else // Must be ComboBox or ListBox
  766.             return output_var.Assign();  // Let ErrorLevel tell the story.
  767.         if (!SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 2000, &index)
  768.             || index == CB_ERR  // CB_ERR == LB_ERR.  There is no selection (or very rarely, some other type of problem).
  769.             || !SendMessageTimeout(control_window, x_msg, (WPARAM)index, 0, SMTO_ABORTIFHUNG, 2000, &length)
  770.             || length == CB_ERR)  // CB_ERR == LB_ERR
  771.             return output_var.Assign(); // Above relies on short-circuit boolean order.
  772.         // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  773.         // being when the item's text is retrieved.  This should be harmless, since there are many
  774.         // other precedents where a variable is sized to something larger than it winds up carrying.
  775.         // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  776.         // this call will set up the clipboard for writing:
  777.         if (output_var.Assign(NULL, (VarSizeType)length) != OK) // It already displayed the error.
  778.             return FAIL;
  779.         if (!SendMessageTimeout(control_window, y_msg, (WPARAM)index, (LPARAM)output_var.Contents()
  780.             , SMTO_ABORTIFHUNG, 2000, &length)
  781.             || length == CB_ERR) // Probably impossible given the way it was called above.  Also, CB_ERR == LB_ERR. Relies on short-circuit boolean order.
  782.         {
  783.             output_var.Close(); // In case it's the clipboard.
  784.             return output_var.Assign(); // Let ErrorLevel tell the story.
  785.         }
  786.         output_var.Close(); // In case it's the clipboard.
  787.         output_var.Length() = length;  // Update to actual vs. estimated length.
  788.         break;
  789.  
  790.     case CONTROLGET_CMD_LIST:
  791.         if (!*aControl) // Fix for v1.0.46.11: If aControl is blank, the control ID came in via a WinTitle of "ahk_id xxx".
  792.         {
  793.             GetClassName(control_window, temp_buf, sizeof(temp_buf));
  794.             aControl = temp_buf;
  795.         }
  796.         if (!strnicmp(aControl, "SysListView32", 13)) // Tried strcasestr(aControl, "ListView") to get it to work with IZArc's Delphi TListView1, but none of the modes or options worked.
  797.             return ControlGetListView(output_var, control_window, aValue); // It will also set ErrorLevel to "success" if successful.
  798.         // This is done here as the special LIST sub-command rather than just being built into
  799.         // ControlGetText because ControlGetText already has a function for ComboBoxes: it fetches
  800.         // the current selection.  Although ListBox does not have such a function, it seem best
  801.         // to consolidate both methods here.
  802.         if (strcasestr(aControl, "Combo")) // v1.0.42: Changed to strcasestr vs. strnicmp for TListBox/TComboBox.
  803.         {
  804.             msg = CB_GETCOUNT;
  805.             x_msg = CB_GETLBTEXTLEN;
  806.             y_msg = CB_GETLBTEXT;
  807.         }
  808.         else if (strcasestr(aControl, "List"))
  809.         {
  810.             msg = LB_GETCOUNT;
  811.             x_msg = LB_GETTEXTLEN;
  812.             y_msg = LB_GETTEXT;
  813.         }
  814.         else // Must be ComboBox or ListBox
  815.             return output_var.Assign();  // Let ErrorLevel tell the story.
  816.         if (!(SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 5000, &item_count))
  817.             || item_count < 1) // No items in ListBox/ComboBox or there was a problem getting the count.
  818.             return output_var.Assign();  // Let ErrorLevel tell the story.
  819.         // Calculate the length of delimited list of items.  Length is initialized to provide enough
  820.         // room for each item's delimiter (the last item does not have a delimiter).
  821.         for (length = item_count - 1, u = 0; u < item_count; ++u)
  822.         {
  823.             if (!SendMessageTimeout(control_window, x_msg, u, 0, SMTO_ABORTIFHUNG, 5000, &item_length)
  824.                 || item_length == LB_ERR) // Note that item_length is legitimately zero for a blank item in the list.
  825.                 return output_var.Assign();  // Let ErrorLevel tell the story.
  826.             length += item_length;
  827.         }
  828.         // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  829.         // being when the item's text is retrieved.  This should be harmless, since there are many
  830.         // other precedents where a variable is sized to something larger than it winds up carrying.
  831.         // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  832.         // this call will set up the clipboard for writing:
  833.         if (output_var.Assign(NULL, (VarSizeType)length, true, true) != OK)
  834.             return FAIL;  // It already displayed the error.
  835.         for (cp = output_var.Contents(), length = item_count - 1, u = 0; u < item_count; ++u)
  836.         {
  837.             if (SendMessageTimeout(control_window, y_msg, (WPARAM)u, (LPARAM)cp, SMTO_ABORTIFHUNG, 5000, &item_length)
  838.                 && item_length != LB_ERR)
  839.             {
  840.                 length += item_length; // Accumulate actual vs. estimated length.
  841.                 cp += item_length;  // Point it to the terminator in preparation for the next write.
  842.             }
  843.             //else do nothing, just consider this to be a blank item so that the process can continue.
  844.             if (u < item_count - 1)
  845.                 *cp++ = '\n'; // Add delimiter after each item except the last (helps parsing loop).
  846.             // Above: In this case, seems better to use \n rather than pipe as default delimiter in case
  847.             // the listbox/combobox contains any real pipes.
  848.         }
  849.         output_var.Close(); // In case it's the clipboard.
  850.         output_var.Length() = (VarSizeType)length;  // Update it to the actual length, which can vary from the estimate.
  851.         break;
  852.  
  853.     case CONTROLGET_CMD_LINECOUNT:  //Must be an Edit
  854.         // MSDN: "If the control has no text, the return value is 1. The return value will never be less than 1."
  855.         if (!SendMessageTimeout(control_window, EM_GETLINECOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  856.             return output_var.Assign();
  857.         output_var.Assign(dwResult);
  858.         break;
  859.  
  860.     case CONTROLGET_CMD_CURRENTLINE:
  861.         if (!SendMessageTimeout(control_window, EM_LINEFROMCHAR, -1, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  862.             return output_var.Assign();
  863.         output_var.Assign(dwResult + 1);
  864.         break;
  865.  
  866.     case CONTROLGET_CMD_CURRENTCOL:
  867.     {
  868.         DWORD line_number;
  869.         // The dwResult from the first msg below is not useful and is not checked.
  870.         if (   !SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult)
  871.             || !SendMessageTimeout(control_window, EM_LINEFROMCHAR, (WPARAM)start, 0, SMTO_ABORTIFHUNG, 2000, &line_number)   )
  872.             return output_var.Assign();
  873.         if (!line_number) // Since we're on line zero, the column number is simply start+1.
  874.         {
  875.             output_var.Assign(start + 1);  // +1 to convert from zero based.
  876.             break; // Fall out of the switch so that ErrorLevel will be set to 0 (no error).
  877.         }
  878.         // Au3: Decrement the character index until the row changes.  Difference between this
  879.         // char index and original is the column:
  880.         DWORD start_orig = start;  // Au3: the character index
  881.         for (;;)
  882.         {
  883.             if (!SendMessageTimeout(control_window, EM_LINEFROMCHAR, (WPARAM)start, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  884.                 return output_var.Assign();
  885.             if (dwResult != line_number)
  886.                 break;
  887.             --start;
  888.         }
  889.         output_var.Assign((int)(start_orig - start));
  890.         break;
  891.     }
  892.  
  893.     case CONTROLGET_CMD_LINE:
  894.         if (!*aValue)
  895.             return output_var.Assign();
  896.         control_index = ATOI(aValue) - 1;
  897.         if (control_index < 0)
  898.             return output_var.Assign();  // Let ErrorLevel tell the story.
  899.         dyn_buf = (char *)_alloca(32768); // 32768 is the size Au3 uses for GETLINE and such.
  900.         *(LPINT)dyn_buf = 32768; // EM_GETLINE requires first word of string to be set to its size.
  901.         if (   !SendMessageTimeout(control_window, EM_GETLINE, (WPARAM)control_index, (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 2000, &dwResult)
  902.             || !dwResult   ) // due to the specified line number being greater than the number of lines in the edit control.
  903.             return output_var.Assign();
  904.         dyn_buf[dwResult] = '\0'; // Ensure terminated since the API might not do it in some cases.
  905.         output_var.Assign(dyn_buf);
  906.         break;
  907.  
  908.     case CONTROLGET_CMD_SELECTED: // Must be an Edit.
  909.         // Note: The RichEdit controls of certain apps such as Metapad don't return the right selection
  910.         // with this technique.  Au3 has the same problem with them, so for now it's just documented here
  911.         // as a limitation.
  912.         if (!SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult))
  913.             return output_var.Assign();
  914.         // The above sets start to be the zero-based position of the start of the selection (similar for end).
  915.         // If there is no selection, start and end will be equal, at least in the edit controls I tried it with.
  916.         // The dwResult from the above is not useful and is not checked.
  917.         if (start == end) // Unlike Au3, it seems best to consider a blank selection to be a non-error.
  918.         {
  919.             output_var.Assign();
  920.             break; // Fall out of the switch so that ErrorLevel will be set to 0 (no error).
  921.         }
  922.         // Dynamic memory is used because must get all the control's text so that just the selected region
  923.         // can be cropped out and assigned to the output variable.  Otherwise, output_var might
  924.         // have to be sized much larger than it would need to be:
  925.         if (   !SendMessageTimeout(control_window, WM_GETTEXTLENGTH, 0, 0, SMTO_ABORTIFHUNG, 2000, &length)
  926.             || !length  // Since the above didn't return for start == end, this is an error because we have a selection of non-zero length, but no text to go with it!
  927.             || !(dyn_buf = (char *)malloc(length + 1))   ) // Relies on short-circuit boolean order.
  928.             return output_var.Assign();
  929.         if (   !SendMessageTimeout(control_window, WM_GETTEXT, (WPARAM)(length + 1), (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 2000, &length)
  930.             || !length || end > length   )
  931.         {
  932.             // The first check above is reveals a problem (ErrorLevel = 1) since the length
  933.             // is unexpectedly zero (above implied it shouldn't be).  The second check is also
  934.             // a problem because the end of the selection should not be beyond length of text
  935.             // that was retrieved.
  936.             free(dyn_buf);
  937.             return output_var.Assign();
  938.         }
  939.         dyn_buf[end] = '\0'; // Terminate the string at the end of the selection.
  940.         output_var.Assign(dyn_buf + start);
  941.         free(dyn_buf);
  942.         break;
  943.  
  944.     case CONTROLGET_CMD_STYLE:
  945.         // Seems best to always format as hex, since it has more human-readable meaning then:
  946.         sprintf(temp_buf, "0x%08X", GetWindowLong(control_window, GWL_STYLE));
  947.         output_var.Assign(temp_buf);
  948.         break;
  949.  
  950.     case CONTROLGET_CMD_EXSTYLE:
  951.         // Seems best to always format as hex, since it has more human-readable meaning then:
  952.         sprintf(temp_buf, "0x%08X", GetWindowLong(control_window, GWL_EXSTYLE));
  953.         output_var.Assign(temp_buf);
  954.         break;
  955.  
  956.     case CONTROLGET_CMD_HWND:
  957.         // The terminology "HWND" was chosen rather than "ID" to avoid confusion with a control's
  958.         // dialog ID (as retrieved by GetDlgCtrlID).  This also reserves the word ID for possible
  959.         // use with the control's Dialog ID in future versions.
  960.         output_var.AssignHWND(control_window);
  961.         break;
  962.     }
  963.  
  964.     // Note that ControlDelay is not done for the Get type commands, because it seems unnecessary.
  965.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  966. }
  967.  
  968.  
  969.  
  970. ResultType Line::URLDownloadToFile(char *aURL, char *aFilespec)
  971. {
  972.     // Check that we have IE3 and access to wininet.dll
  973.     HINSTANCE hinstLib = LoadLibrary("wininet");
  974.     if (!hinstLib)
  975.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  976.  
  977.     typedef HINTERNET (WINAPI *MyInternetOpen)(LPCTSTR, DWORD, LPCTSTR, LPCTSTR, DWORD dwFlags);
  978.     typedef HINTERNET (WINAPI *MyInternetOpenUrl)(HINTERNET hInternet, LPCTSTR, LPCTSTR, DWORD, DWORD, LPDWORD);
  979.     typedef BOOL (WINAPI *MyInternetCloseHandle)(HINTERNET);
  980.     typedef BOOL (WINAPI *MyInternetReadFileEx)(HINTERNET, LPINTERNET_BUFFERS, DWORD, DWORD);
  981.  
  982.     #ifndef INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY
  983.         #define INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY 4
  984.     #endif
  985.  
  986.     // Get the address of all the functions we require.  It's done this way in case the system
  987.     // lacks MSIE v3.0+, in which case the app would probably refuse to launch at all:
  988.      MyInternetOpen lpfnInternetOpen = (MyInternetOpen)GetProcAddress(hinstLib, "InternetOpenA");
  989.     MyInternetOpenUrl lpfnInternetOpenUrl = (MyInternetOpenUrl)GetProcAddress(hinstLib, "InternetOpenUrlA");
  990.     MyInternetCloseHandle lpfnInternetCloseHandle = (MyInternetCloseHandle)GetProcAddress(hinstLib, "InternetCloseHandle");
  991.     MyInternetReadFileEx lpfnInternetReadFileEx = (MyInternetReadFileEx)GetProcAddress(hinstLib, "InternetReadFileExA");
  992.     if (!(lpfnInternetOpen && lpfnInternetOpenUrl && lpfnInternetCloseHandle && lpfnInternetReadFileEx))
  993.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  994.  
  995.     // v1.0.44.07: Set default to INTERNET_FLAG_RELOAD vs. 0 because the vast majority of usages would want
  996.     // the file to be retrieved directly rather than from the cache.
  997.     // v1.0.46.04: Added more no-cache flags because otherwise, it definitely falls back to the cache if
  998.     // the remote server doesn't repond (and perhaps other errors), which defeats the ability to use
  999.     // UrlDownloadToFile for uptime/server monitoring.  Also, in spite of what MSDN says, it seems nearly
  1000.     // certain based on other sources that more than one flag is supported.  Someone also mentioned that
  1001.     // INTERNET_FLAG_CACHE_IF_NET_FAIL is related to this, but there's no way to specify it in these
  1002.     // particular calls, and it's the opposite of the desired behavior anyway; so it seems impossible to
  1003.     // turn it off explicitly.
  1004.     DWORD flags_for_open_url = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_CACHE_WRITE;
  1005.     aURL = omit_leading_whitespace(aURL);
  1006.     if (*aURL == '*') // v1.0.44.07: Provide an option to override flags_for_open_url.
  1007.     {
  1008.         flags_for_open_url = ATOU(++aURL);
  1009.         char *cp;
  1010.         if (cp = StrChrAny(aURL, " \t")) // Find first space or tab.
  1011.             aURL = omit_leading_whitespace(cp);
  1012.     }
  1013.  
  1014.     // Open the internet session. v1.0.45.03: Provide a non-NULL user-agent because  some servers reject
  1015.     // requests that lack a user-agent.  Furthermore, it's more professional to have one, in which case it
  1016.     // should probably be kept as simple and unchanging as possible.  Using something like the script's name
  1017.     // as the user agent (even if documented) seems like a bad idea because it might contain personal/sensitive info.
  1018.     HINTERNET hInet = lpfnInternetOpen("AutoHotkey", INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);
  1019.     if (!hInet)
  1020.     {
  1021.         FreeLibrary(hinstLib);
  1022.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1023.     }
  1024.  
  1025.     // Open the required URL
  1026.     HINTERNET hFile = lpfnInternetOpenUrl(hInet, aURL, NULL, 0, flags_for_open_url, 0);
  1027.     if (!hFile)
  1028.     {
  1029.         lpfnInternetCloseHandle(hInet);
  1030.         FreeLibrary(hinstLib);
  1031.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1032.     }
  1033.  
  1034.     // Open our output file
  1035.     FILE *fptr = fopen(aFilespec, "wb");    // Open in binary write/destroy mode
  1036.     if (!fptr)
  1037.     {
  1038.         lpfnInternetCloseHandle(hFile);
  1039.         lpfnInternetCloseHandle(hInet);
  1040.         FreeLibrary(hinstLib);
  1041.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1042.     }
  1043.  
  1044.     BYTE bufData[1024 * 1]; // v1.0.44.11: Reduced from 8 KB to alleviate GUI window lag during UrlDownloadtoFile.  Testing shows this reduction doesn't affect performance on high-speed downloads (in fact, downloads are slightly faster; I tested two sites, one at 184 KB/s and the other at 380 KB/s).  It might affect slow downloads, but that seems less likely so wasn't tested.
  1045.     INTERNET_BUFFERS buffers = {0};
  1046.     buffers.dwStructSize = sizeof(INTERNET_BUFFERS);
  1047.     buffers.lpvBuffer = bufData;
  1048.     buffers.dwBufferLength = sizeof(bufData);
  1049.  
  1050.     LONG_OPERATION_INIT
  1051.  
  1052.     // Read the file.  I don't think synchronous transfers typically generate the pseudo-error
  1053.     // ERROR_IO_PENDING, so that is not checked here.  That's probably just for async transfers.
  1054.     // IRF_NO_WAIT is used to avoid requiring the call to block until the buffer is full.  By
  1055.     // having it return the moment there is any data in the buffer, the program is made more
  1056.     // responsive, especially when the download is very slow and/or one of the hooks is installed:
  1057.     BOOL result;
  1058.     while (result = lpfnInternetReadFileEx(hFile, &buffers, IRF_NO_WAIT, NULL)) // Assign
  1059.     {
  1060.         if (!buffers.dwBufferLength) // Transfer is complete.
  1061.             break;
  1062.         LONG_OPERATION_UPDATE  // Done in between the net-read and the file-write to improve avg. responsiveness.
  1063.         fwrite(bufData, buffers.dwBufferLength, 1, fptr);
  1064.         buffers.dwBufferLength = sizeof(bufData);  // Reset buffer capacity for next iteration.
  1065.     }
  1066.  
  1067.     // Close internet session:
  1068.     lpfnInternetCloseHandle(hFile);
  1069.     lpfnInternetCloseHandle(hInet);
  1070.     FreeLibrary(hinstLib); // Only after the above.
  1071.     // Close output file:
  1072.     fclose(fptr);
  1073.  
  1074.     if (result)
  1075.         return g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success.
  1076.     else // An error occurred during the transfer.
  1077.     {
  1078.         DeleteFile(aFilespec);  // delete damaged/incomplete file
  1079.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1080.     }
  1081. }
  1082.  
  1083.  
  1084.  
  1085. int CALLBACK FileSelectFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
  1086. {
  1087.     if (uMsg == BFFM_INITIALIZED) // Caller has ensured that lpData isn't NULL by having set a valid lParam value.
  1088.         SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
  1089.     // In spite of the quote below, the behavior does not seem to vary regardless of what value is returned
  1090.     // upon receipt of BFFM_VALIDATEFAILED, at least on XP.  But in case it matters on other OSes, preserve
  1091.     // compatibilty with versions older than 1.0.36.03 by keeping the dialog displayed even if the user enters
  1092.     // an invalid folder:
  1093.     // MSDN: "Returns zero except in the case of BFFM_VALIDATEFAILED. For that flag, returns zero to dismiss
  1094.     // the dialog or nonzero to keep the dialog displayed."
  1095.     return uMsg == BFFM_VALIDATEFAILED; // i.e. zero should be returned in almost every case.
  1096. }
  1097.  
  1098.  
  1099.  
  1100. ResultType Line::FileSelectFolder(char *aRootDir, char *aOptions, char *aGreeting)
  1101. // Since other script threads can interrupt this command while it's running, it's important that
  1102. // the command not refer to sArgDeref[] and sArgVar[] anytime after an interruption becomes possible.
  1103. // This is because an interrupting thread usually changes the values to something inappropriate for this thread.
  1104. {
  1105.     Var &output_var = *OUTPUT_VAR; // Must be resolved early.  See comment above.
  1106.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  1107.     if (!output_var.Assign())  // Initialize the output variable.
  1108.         return FAIL;
  1109.  
  1110.     if (g_nFolderDialogs >= MAX_FOLDERDIALOGS)
  1111.     {
  1112.         // Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
  1113.         MsgBox("The maximum number of Folder Dialogs has been reached." ERR_ABORT);
  1114.         return FAIL;
  1115.     }
  1116.  
  1117.     LPMALLOC pMalloc;
  1118.     if (SHGetMalloc(&pMalloc) != NOERROR)    // Initialize
  1119.         return OK;  // Let ErrorLevel tell the story.
  1120.  
  1121.     // v1.0.36.03: Support initial folder, which is different than the root folder because the root only
  1122.     // controls the origin point (above which the control cannot navigate).
  1123.     char *initial_folder;
  1124.     char root_dir[MAX_PATH*2 + 5];  // Up to two paths might be present inside, including an asterisk and spaces between them.
  1125.     strlcpy(root_dir, aRootDir, sizeof(root_dir)); // Make a modifiable copy.
  1126.     if (initial_folder = strchr(root_dir, '*'))
  1127.     {
  1128.         *initial_folder = '\0'; // Terminate so that root_dir becomes an isolated string.
  1129.         // Must eliminate the trailing whitespace or it won't work.  However, only up to one space or tab
  1130.         // so that path names that really do end in literal spaces can be used:
  1131.         if (initial_folder > root_dir && IS_SPACE_OR_TAB(initial_folder[-1]))
  1132.             initial_folder[-1] = '\0';
  1133.         // In case absolute paths can ever have literal leading whitespace, preserve that whitespace
  1134.         // by incremently by only one and not calling omit_leading_whitespace().  This has been documented.
  1135.         ++initial_folder;
  1136.     }
  1137.     else
  1138.         initial_folder = NULL;
  1139.     if (!*(omit_leading_whitespace(root_dir))) // Count all-whitespace as a blank string, but retain leading whitespace if there is also non-whitespace inside.
  1140.         *root_dir = '\0';
  1141.  
  1142.     BROWSEINFO bi;
  1143.     if (initial_folder)
  1144.     {
  1145.         bi.lpfn = FileSelectFolderCallback;
  1146.         bi.lParam = (LPARAM)initial_folder;  // Used by the callback above.
  1147.     }
  1148.     else
  1149.         bi.lpfn = NULL;  // It will ignore the value of bi.lParam when lpfn is NULL.
  1150.  
  1151.     if (*root_dir)
  1152.     {
  1153.         IShellFolder *pDF;
  1154.         if (SHGetDesktopFolder(&pDF) == NOERROR)
  1155.         {
  1156.             LPITEMIDLIST pIdl = NULL;
  1157.             ULONG        chEaten;
  1158.             ULONG        dwAttributes;
  1159.             OLECHAR olePath[MAX_PATH];            // wide-char version of path name
  1160.             ToWideChar(root_dir, olePath, MAX_PATH); // Dest. size is in wchars, not bytes.
  1161.             pDF->ParseDisplayName(NULL, NULL, olePath, &chEaten, &pIdl, &dwAttributes);
  1162.             pDF->Release();
  1163.             bi.pidlRoot = pIdl;
  1164.         }
  1165.     }
  1166.     else // No root directory.
  1167.         bi.pidlRoot = NULL;  // Make it use "My Computer" as the root dir.
  1168.  
  1169.     int iImage = 0;
  1170.     bi.iImage = iImage;
  1171.     bi.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used rather than main window since no need to have main window forced into the background by this.
  1172.     char greeting[1024];
  1173.     if (aGreeting && *aGreeting)
  1174.         strlcpy(greeting, aGreeting, sizeof(greeting));
  1175.     else
  1176.         snprintf(greeting, sizeof(greeting), "Select Folder - %s", g_script.mFileName);
  1177.     bi.lpszTitle = greeting;
  1178.  
  1179.     DWORD options = *aOptions ? ATOI(aOptions) : FSF_ALLOW_CREATE;
  1180.     bi.ulFlags = 0x0040 | ((options & FSF_ALLOW_CREATE) ? 0 : 0x200) | ((options & (DWORD)FSF_EDITBOX) ? BIF_EDITBOX : 0);
  1181.  
  1182.     char Result[2048];
  1183.     bi.pszDisplayName = Result;  // This will hold the user's choice.
  1184.  
  1185.     // At this point, we know a dialog will be displayed.  See macro's comments for details:
  1186.     DIALOG_PREP
  1187.     POST_AHK_DIALOG(0) // Do this only after the above.  Must pass 0 for timeout in this case.
  1188.  
  1189.     ++g_nFolderDialogs;
  1190.     LPITEMIDLIST lpItemIDList = SHBrowseForFolder(&bi);  // Spawn Dialog
  1191.     --g_nFolderDialogs;
  1192.  
  1193.     DIALOG_END
  1194.     if (!lpItemIDList)
  1195.         return OK;  // Let ErrorLevel tell the story.
  1196.  
  1197.     *Result = '\0';  // Reuse this var, this time to old the result of the below:
  1198.     SHGetPathFromIDList(lpItemIDList, Result);
  1199.     pMalloc->Free(lpItemIDList);
  1200.     pMalloc->Release();
  1201.  
  1202.     g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  1203.     return output_var.Assign(Result);
  1204. }
  1205.  
  1206.  
  1207.  
  1208. ResultType Line::FileGetShortcut(char *aShortcutFile) // Credited to Holger <Holger.Kotsch at GMX de>.
  1209. {
  1210.     Var *output_var_target = ARGVAR2; // These might be omitted in the parameter list, so it's okay if 
  1211.     Var *output_var_dir = ARGVAR3;    // they resolve to NULL.
  1212.     Var *output_var_arg = ARGVAR4;
  1213.     Var *output_var_desc = ARGVAR5;
  1214.     Var *output_var_icon = ARGVAR6;
  1215.     Var *output_var_icon_idx = ARGVAR7;
  1216.     Var *output_var_show_state = ARGVAR8;
  1217.  
  1218.     // For consistency with the behavior of other commands, the output variables are initialized to blank
  1219.     // so that there is another way to detect failure:
  1220.     if (output_var_target) output_var_target->Assign();
  1221.     if (output_var_dir) output_var_dir->Assign();
  1222.     if (output_var_arg) output_var_arg->Assign();
  1223.     if (output_var_desc) output_var_desc->Assign();
  1224.     if (output_var_icon) output_var_icon->Assign();
  1225.     if (output_var_icon_idx) output_var_icon_idx->Assign();
  1226.     if (output_var_show_state) output_var_show_state->Assign();
  1227.  
  1228.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  1229.  
  1230.     if (!Util_DoesFileExist(aShortcutFile))
  1231.         return OK;  // Let ErrorLevel tell the story.
  1232.  
  1233.     CoInitialize(NULL);
  1234.     IShellLink *psl;
  1235.  
  1236.     if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
  1237.     {
  1238.         IPersistFile *ppf;
  1239.         if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf)))
  1240.         {
  1241.             WCHAR wsz[MAX_PATH+1]; // +1 hasn't been explained, but is retained in case it's needed.
  1242.             ToWideChar(aShortcutFile, wsz, MAX_PATH+1); // Dest. size is in wchars, not bytes.
  1243.             if (SUCCEEDED(ppf->Load((const WCHAR*)wsz, 0)))
  1244.             {
  1245.                 char buf[MAX_PATH+1];
  1246.                 int icon_index, show_cmd;
  1247.  
  1248.                 if (output_var_target)
  1249.                 {
  1250.                     psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);
  1251.                     output_var_target->Assign(buf);
  1252.                 }
  1253.                 if (output_var_dir)
  1254.                 {
  1255.                     psl->GetWorkingDirectory(buf, MAX_PATH);
  1256.                     output_var_dir->Assign(buf);
  1257.                 }
  1258.                 if (output_var_arg)
  1259.                 {
  1260.                     psl->GetArguments(buf, MAX_PATH);
  1261.                     output_var_arg->Assign(buf);
  1262.                 }
  1263.                 if (output_var_desc)
  1264.                 {
  1265.                     psl->GetDescription(buf, MAX_PATH); // Testing shows that the OS limits it to 260 characters.
  1266.                     output_var_desc->Assign(buf);
  1267.                 }
  1268.                 if (output_var_icon || output_var_icon_idx)
  1269.                 {
  1270.                     psl->GetIconLocation(buf, MAX_PATH, &icon_index);
  1271.                     if (output_var_icon)
  1272.                         output_var_icon->Assign(buf);
  1273.                     if (output_var_icon_idx)
  1274.                         if (*buf)
  1275.                             output_var_icon_idx->Assign(icon_index + 1);  // Convert from 0-based to 1-based for consistency with the Menu command, etc.
  1276.                         else
  1277.                             output_var_icon_idx->Assign(); // Make it blank to indicate that there is none.
  1278.                 }
  1279.                 if (output_var_show_state)
  1280.                 {
  1281.                     psl->GetShowCmd(&show_cmd);
  1282.                     output_var_show_state->Assign(show_cmd);
  1283.                     // For the above, decided not to translate them to Max/Min/Normal since other
  1284.                     // show-state numbers might be supported in the future (or are already).  In other
  1285.                     // words, this allows the flexibilty to specify some number other than 1/3/7 when
  1286.                     // creating the shortcut in case it happens to work.  Of course, that applies only
  1287.                     // to FileCreateShortcut, not here.  But it's done here so that this command is
  1288.                     // compatible with that one.
  1289.                 }
  1290.                 g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  1291.             }
  1292.             ppf->Release();
  1293.         }
  1294.         psl->Release();
  1295.     }
  1296.     CoUninitialize();
  1297.  
  1298.     return OK;  // ErrorLevel might still indicate failture if one of the above calls failed.
  1299. }
  1300.  
  1301.  
  1302.  
  1303. ResultType Line::FileCreateShortcut(char *aTargetFile, char *aShortcutFile, char *aWorkingDir, char *aArgs
  1304.     , char *aDescription, char *aIconFile, char *aHotkey, char *aIconNumber, char *aRunState)
  1305. {
  1306.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
  1307.     CoInitialize(NULL);
  1308.     IShellLink *psl;
  1309.  
  1310.     if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
  1311.     {
  1312.         psl->SetPath(aTargetFile);
  1313.         if (*aWorkingDir)
  1314.             psl->SetWorkingDirectory(aWorkingDir);
  1315.         if (*aArgs)
  1316.             psl->SetArguments(aArgs);
  1317.         if (*aDescription)
  1318.             psl->SetDescription(aDescription);
  1319.         if (*aIconFile)
  1320.             psl->SetIconLocation(aIconFile, *aIconNumber ? ATOI(aIconNumber) - 1 : 0); // Doesn't seem necessary to validate aIconNumber as not being negative, etc.
  1321.         if (*aHotkey)
  1322.         {
  1323.             // If badly formatted, it's not a critical error, just continue.
  1324.             // Currently, only shortcuts with a CTRL+ALT are supported.
  1325.             // AutoIt3 note: Make sure that CTRL+ALT is selected (otherwise invalid)
  1326.             vk_type vk = TextToVK(aHotkey);
  1327.             if (vk)
  1328.                 // Vk in low 8 bits, mods in high 8:
  1329.                 psl->SetHotkey(   (WORD)vk | ((WORD)(HOTKEYF_CONTROL | HOTKEYF_ALT) << 8)   );
  1330.         }
  1331.         if (*aRunState)
  1332.             psl->SetShowCmd(ATOI(aRunState)); // No validation is done since there's a chance other numbers might be valid now or in the future.
  1333.  
  1334.         IPersistFile *ppf;
  1335.         if (SUCCEEDED(psl->QueryInterface(IID_IPersistFile,(LPVOID *)&ppf)))
  1336.         {
  1337.             WCHAR wsz[MAX_PATH];
  1338.             ToWideChar(aShortcutFile, wsz, MAX_PATH); // Dest. size is in wchars, not bytes.
  1339.             if (SUCCEEDED(ppf->Save((LPCWSTR)wsz, TRUE)))
  1340.                 g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
  1341.             ppf->Release();
  1342.         }
  1343.         psl->Release();
  1344.     }
  1345.  
  1346.     CoUninitialize();
  1347.     return OK; // ErrorLevel indicates whether or not it succeeded.
  1348. }
  1349.  
  1350.  
  1351.  
  1352. ResultType Line::FileRecycle(char *aFilePattern)
  1353. {
  1354.     if (!aFilePattern || !*aFilePattern)
  1355.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);  // Since this is probably not what the user intended.
  1356.  
  1357.     SHFILEOPSTRUCT FileOp;
  1358.     char szFileTemp[_MAX_PATH+2];
  1359.  
  1360.     // au3: Get the fullpathname - required for UNDO to work
  1361.     Util_GetFullPathName(aFilePattern, szFileTemp);
  1362.  
  1363.     // au3: We must also make it a double nulled string *sigh*
  1364.     szFileTemp[strlen(szFileTemp)+1] = '\0';    
  1365.  
  1366.     // au3: set to known values - Corrects crash
  1367.     FileOp.hNameMappings = NULL;
  1368.     FileOp.lpszProgressTitle = NULL;
  1369.     FileOp.fAnyOperationsAborted = FALSE;
  1370.     FileOp.hwnd = NULL;
  1371.     FileOp.pTo = NULL;
  1372.  
  1373.     FileOp.pFrom = szFileTemp;
  1374.     FileOp.wFunc = FO_DELETE;
  1375.     FileOp.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
  1376.  
  1377.     // SHFileOperation() returns 0 on success:
  1378.     return g_ErrorLevel->Assign(SHFileOperation(&FileOp) ? ERRORLEVEL_ERROR : ERRORLEVEL_NONE);
  1379. }
  1380.  
  1381.  
  1382.  
  1383. ResultType Line::FileRecycleEmpty(char *aDriveLetter)
  1384. {
  1385.     // Not using GetModuleHandle() because there is doubt that SHELL32 (unlike USER32/KERNEL32), is
  1386.     // always automatically present in every process (e.g. if shell is something other than Explorer):
  1387.     HINSTANCE hinstLib = LoadLibrary("shell32");
  1388.     if (!hinstLib)
  1389.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1390.     // au3: Get the address of all the functions we require
  1391.     typedef HRESULT (WINAPI *MySHEmptyRecycleBin)(HWND, LPCTSTR, DWORD);
  1392.      MySHEmptyRecycleBin lpfnEmpty = (MySHEmptyRecycleBin)GetProcAddress(hinstLib, "SHEmptyRecycleBinA");
  1393.     if (!lpfnEmpty)
  1394.     {
  1395.         FreeLibrary(hinstLib);
  1396.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1397.     }
  1398.     const char *szPath = *aDriveLetter ? aDriveLetter : NULL;
  1399.     if (lpfnEmpty(NULL, szPath, SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND) != S_OK)
  1400.     {
  1401.         FreeLibrary(hinstLib);
  1402.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1403.     }
  1404.     FreeLibrary(hinstLib);
  1405.     return g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  1406. }
  1407.  
  1408.  
  1409.  
  1410. ResultType Line::FileGetVersion(char *aFilespec)
  1411. {
  1412.     g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default
  1413.     OUTPUT_VAR->Assign(); // Init to be blank, in case of failure.
  1414.  
  1415.     if (!aFilespec || !*aFilespec)
  1416.         return OK;  // Let ErrorLevel indicate an error, since this is probably not what the user intended.
  1417.  
  1418.     DWORD dwUnused, dwSize;
  1419.     if (   !(dwSize = GetFileVersionInfoSize(aFilespec, &dwUnused))   )  // No documented limit on how large it can be, so don't use _alloca().
  1420.         return OK;  // Let ErrorLevel tell the story.
  1421.  
  1422.     BYTE *pInfo = (BYTE*)malloc(dwSize);  // Allocate the size retrieved by the above.
  1423.  
  1424.     // Read the version resource
  1425.     GetFileVersionInfo((LPSTR)aFilespec, 0, dwSize, (LPVOID)pInfo);
  1426.  
  1427.     // Locate the fixed information
  1428.     VS_FIXEDFILEINFO *pFFI;
  1429.     UINT uSize;
  1430.     if (!VerQueryValue(pInfo, "\\", (LPVOID *)&pFFI, &uSize))
  1431.     {
  1432.         free(pInfo);
  1433.         return OK;  // Let ErrorLevel tell the story.
  1434.     }
  1435.  
  1436.     // extract the fields you want from pFFI
  1437.     UINT iFileMS = (UINT)pFFI->dwFileVersionMS;
  1438.     UINT iFileLS = (UINT)pFFI->dwFileVersionLS;
  1439.     char version_string[128];  // AutoIt3: 43+1 is the maximum size, but leave a little room to increase confidence.
  1440.     snprintf(version_string, sizeof(version_string), "%u.%u.%u.%u"
  1441.         , (iFileMS >> 16), (iFileMS & 0xFFFF), (iFileLS >> 16), (iFileLS & 0xFFFF));
  1442.  
  1443.     free(pInfo);
  1444.  
  1445.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Indicate success.
  1446.     return OUTPUT_VAR->Assign(version_string);
  1447. }
  1448.  
  1449.  
  1450.  
  1451. bool Line::Util_CopyDir(const char *szInputSource, const char *szInputDest, bool bOverwrite)
  1452. {
  1453.     // Get the fullpathnames and strip trailing \s
  1454.     char szSource[_MAX_PATH+2];
  1455.     char szDest[_MAX_PATH+2];
  1456.     Util_GetFullPathName(szInputSource, szSource);
  1457.     Util_GetFullPathName(szInputDest, szDest);
  1458.  
  1459.     // Ensure source is a directory
  1460.     if (Util_IsDir(szSource) == false)
  1461.         return false;                            // Nope
  1462.  
  1463.     // Does the destination dir exist?
  1464.     if (Util_IsDir(szDest))
  1465.     {
  1466.         if (bOverwrite == false)
  1467.             return false;
  1468.     }
  1469.     else // Although dest doesn't exist as a dir, it might be a file, which is covered below too.
  1470.     {
  1471.         // We must create the top level directory
  1472.         if (!Util_CreateDir(szDest)) // Failure is expected to happen if szDest is an existing *file*, since a dir should never be allowed to overwrite a file (to avoid accidental loss of data).
  1473.             return false;
  1474.     }
  1475.  
  1476.     // To work under old versions AND new version of shell32.dll the source must be specifed
  1477.     // as "dir\*.*" and the destination directory must already exist... Godamn Microsoft and their APIs...
  1478.     strcat(szSource, "\\*.*");
  1479.  
  1480.     // We must also make source\dest double nulled strings for the SHFileOp API
  1481.     szSource[strlen(szSource)+1] = '\0';    
  1482.     szDest[strlen(szDest)+1] = '\0';    
  1483.  
  1484.     // Setup the struct
  1485.     SHFILEOPSTRUCT FileOp = {0};
  1486.     FileOp.pFrom = szSource;
  1487.     FileOp.pTo = szDest;
  1488.     FileOp.wFunc = FO_COPY;
  1489.     FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI; // FOF_NO_UI ("perform the operation with no user input") is not present for in case it would break compatibility somehow, and because the other flags already present seem to make its behavior implicit.  Also, unlike FileMoveDir, FOF_MULTIDESTFILES never seems to be needed.
  1490.     // All of the below left set to NULL/FALSE by the struct initializer higher above:
  1491.     //FileOp.hNameMappings            = NULL;
  1492.     //FileOp.lpszProgressTitle        = NULL;
  1493.     //FileOp.fAnyOperationsAborted    = FALSE;
  1494.     //FileOp.hwnd                    = NULL;
  1495.  
  1496.     return !SHFileOperation(&FileOp);
  1497. }
  1498.  
  1499.  
  1500.  
  1501. bool Line::Util_MoveDir(const char *szInputSource, const char *szInputDest, int OverwriteMode)
  1502. {
  1503.     // Get the fullpathnames and strip trailing \s
  1504.     char szSource[_MAX_PATH+2];
  1505.     char szDest[_MAX_PATH+2];
  1506.     Util_GetFullPathName(szInputSource, szSource);
  1507.     Util_GetFullPathName(szInputDest, szDest);
  1508.  
  1509.     // Ensure source is a directory
  1510.     if (Util_IsDir(szSource) == false)
  1511.         return false;                            // Nope
  1512.  
  1513.     // Does the destination dir exist?
  1514.     DWORD attr = GetFileAttributes(szDest);
  1515.     if (attr != 0xFFFFFFFF) // Destination already exists as a file or directory.
  1516.     {
  1517.         if (attr & FILE_ATTRIBUTE_DIRECTORY) // Dest already exists as a directory.
  1518.         {
  1519.             if (OverwriteMode != 1 && OverwriteMode != 2) // Overwrite Mode is "Never".  Strict validation for safety.
  1520.                 return false; // For consistency, mode1 actually should move the source-dir *into* the identically name dest dir.  But for backward compatibility, this change hasn't been made.
  1521.         }
  1522.         else // Dest already exists as a file.
  1523.             return false; // Don't even attempt to overwrite a file with a dir, regardless of mode (I think SHFileOperation refuses to do it anyway).
  1524.     }
  1525.  
  1526.     if (Util_IsDifferentVolumes(szSource, szDest))
  1527.     {
  1528.         // If the source and dest are on different volumes then we must copy rather than move
  1529.         // as move in this case only works on some OSes.  Copy and delete (poor man's move).
  1530.         if (!Util_CopyDir(szSource, szDest, true))
  1531.             return false;
  1532.         return Util_RemoveDir(szSource, true);
  1533.     }
  1534.  
  1535.     // Since above didn't return, source and dest are on same volume.
  1536.     // We must also make source\dest double nulled strings for the SHFileOp API
  1537.     szSource[strlen(szSource)+1] = '\0';
  1538.     szDest[strlen(szDest)+1] = '\0';
  1539.  
  1540.     // Setup the struct
  1541.     SHFILEOPSTRUCT FileOp = {0};
  1542.     FileOp.pFrom = szSource;
  1543.     FileOp.pTo = szDest;
  1544.     FileOp.wFunc = FO_MOVE;
  1545.     FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI; // Set default. FOF_NO_UI ("perform the operation with no user input") is not present for in case it would break compatibility somehow, and because the other flags already present seem to make its behavior implicit.
  1546.     if (OverwriteMode == 2) // v1.0.46.07: Using the FOF_MULTIDESTFILES flag (as hinted by MSDN) overwrites/merges any existing target directory.  This logic supercedes and fixes old logic that didn't work properly when the source dir was being both renamed and moved to overwrite an existing directory.
  1547.         FileOp.fFlags |= FOF_MULTIDESTFILES;
  1548.     // All of the below left set to NULL/FALSE by the struct initializer higher above:
  1549.     //FileOp.hNameMappings            = NULL;
  1550.     //FileOp.lpszProgressTitle        = NULL;
  1551.     //FileOp.fAnyOperationsAborted    = FALSE;
  1552.     //FileOp.hwnd                    = NULL;
  1553.  
  1554.     return !SHFileOperation(&FileOp);
  1555. }
  1556.  
  1557.  
  1558.  
  1559. bool Line::Util_RemoveDir(const char *szInputSource, bool bRecurse)
  1560. {
  1561.     SHFILEOPSTRUCT    FileOp;
  1562.     char            szSource[_MAX_PATH+2];
  1563.  
  1564.     // Get the fullpathnames and strip trailing \s
  1565.     Util_GetFullPathName(szInputSource, szSource);
  1566.  
  1567.     // Ensure source is a directory
  1568.     if (Util_IsDir(szSource) == false)
  1569.         return false;                            // Nope
  1570.  
  1571.     // If recursion not on just try a standard delete on the directory (the SHFile function WILL
  1572.     // delete a directory even if not empty no matter what flags you give it...)
  1573.     if (bRecurse == false)
  1574.     {
  1575.         if (!RemoveDirectory(szSource))
  1576.             return false;
  1577.         else
  1578.             return true;
  1579.     }
  1580.  
  1581.     // We must also make double nulled strings for the SHFileOp API
  1582.     szSource[strlen(szSource)+1] = '\0';
  1583.  
  1584.     // Setup the struct
  1585.     FileOp.pFrom                    = szSource;
  1586.     FileOp.pTo                        = NULL;
  1587.     FileOp.hNameMappings            = NULL;
  1588.     FileOp.lpszProgressTitle        = NULL;
  1589.     FileOp.fAnyOperationsAborted    = FALSE;
  1590.     FileOp.hwnd                        = NULL;
  1591.  
  1592.     FileOp.wFunc    = FO_DELETE;
  1593.     FileOp.fFlags    = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI;
  1594.     
  1595.     return !SHFileOperation(&FileOp);
  1596. }
  1597.  
  1598.  
  1599.  
  1600. ///////////////////////////////////////////////////////////////////////////////
  1601. // Util_CopyFile()
  1602. // (moves files too)
  1603. // Returns the number of files that could not be copied or moved due to error.
  1604. ///////////////////////////////////////////////////////////////////////////////
  1605. int Line::Util_CopyFile(const char *szInputSource, const char *szInputDest, bool bOverwrite, bool bMove)
  1606. {
  1607.     char            szSource[_MAX_PATH+1];
  1608.     char            szDest[_MAX_PATH+1];
  1609.     char            szExpandedDest[MAX_PATH+1];
  1610.     char            szTempPath[_MAX_PATH+1];
  1611.     char            szDrive[_MAX_PATH+1];
  1612.     char            szDir[_MAX_PATH+1];
  1613.     char            szFile[_MAX_PATH+1];
  1614.     char            szExt[_MAX_PATH+1];
  1615.  
  1616.     // Get local version of our source/dest with full path names, strip trailing \s
  1617.     Util_GetFullPathName(szInputSource, szSource);
  1618.     Util_GetFullPathName(szInputDest, szDest);
  1619.  
  1620.     // If the source or dest is a directory then add *.* to the end
  1621.     if (Util_IsDir(szSource))
  1622.         strcat(szSource, "\\*.*");
  1623.     if (Util_IsDir(szDest))
  1624.         strcat(szDest, "\\*.*");
  1625.  
  1626.     WIN32_FIND_DATA    findData;
  1627.     HANDLE hSearch = FindFirstFile(szSource, &findData);
  1628.     if (hSearch == INVALID_HANDLE_VALUE)
  1629.         return 0; // Indicate no failures.
  1630.  
  1631.     // Otherwise, loop through all the matching files.
  1632.     // Split source into file and extension (we need this info in the loop below to recontstruct the path)
  1633.     _splitpath(szSource, szDrive, szDir, szFile, szExt);
  1634.     // Note we now rely on the SOURCE being the contents of szDrive, szDir, szFile, etc.
  1635.     size_t szTempPath_length = snprintf(szTempPath, sizeof(szTempPath), "%s%s", szDrive, szDir);
  1636.     char *append_pos = szTempPath + szTempPath_length;
  1637.     size_t space_remaining = sizeof(szTempPath) - szTempPath_length - 1;
  1638.  
  1639.     int failure_count = 0;
  1640.     LONG_OPERATION_INIT
  1641.  
  1642.     do
  1643.     {
  1644.         // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
  1645.         // this function and those that call it not refer to sArgDeref[] and sArgVar[] anytime after an
  1646.         // interruption becomes possible. This is because an interrupting thread usually changes the
  1647.         // values to something inappropriate for this thread.
  1648.         LONG_OPERATION_UPDATE
  1649.  
  1650.         // Make sure the returned handle is a file and not a directory before we
  1651.         // try and do copy type things on it!
  1652.         if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // dwFileAttributes should never be invalid (0xFFFFFFFF) in this case.
  1653.             continue;
  1654.  
  1655.         if (strlen(findData.cFileName) > space_remaining) // v1.0.45.03: Basic check in case of files whose full spec is over 260 characters long.
  1656.         {
  1657.             ++failure_count;
  1658.             continue;
  1659.         }
  1660.         strcpy(append_pos, findData.cFileName); // Indirectly populate szTempPath. Above has ensured this won't overflow.
  1661.  
  1662.         // Expand the destination based on this found file
  1663.         Util_ExpandFilenameWildcard(findData.cFileName, szDest, szExpandedDest);
  1664.  
  1665.         // Fixed for v1.0.36.01: This section has been revised to avoid unnecessary calls; but more
  1666.         // importantly, it now avoids the deletion and complete loss of a file when it is copied or
  1667.         // moved onto itself.  That used to happen because any existing destination file used to be
  1668.         // deleted prior to attempting the move/copy.
  1669.         if (bMove)  // Move vs. copy mode.
  1670.         {
  1671.             // Note that MoveFile() is capable of moving a file to a different volume, regardless of
  1672.             // operating system version.  That's enough for what we need because this function never
  1673.             // moves directories, only files.
  1674.  
  1675.             // The following call will report success if source and dest are the same file, even if
  1676.             // source is something like "..\Folder\Filename.txt" and dest is something like
  1677.             // "C:\Folder\Filename.txt" (or if source is an 8.3 filename and dest is the long name
  1678.             // of the same file).  This is good because it avoids the need to devise code
  1679.             // to determine whether two different path names refer to the same physical file
  1680.             // (note that GetFullPathName() has shown itself to be inadequate for this purpose due
  1681.             // to problems with short vs. long names, UNC vs. mapped drive, and possibly NTFS hard
  1682.             // links (aliases) that might all cause two different filenames to point to the same
  1683.             // physical file on disk (hopefully MoveFile handles all of these correctly by indicating
  1684.             // success [below] when a file is moved onto itself, though it has only been tested for
  1685.             // basic cases of relative vs. absolute path).
  1686.             if (!MoveFile(szTempPath, szExpandedDest))
  1687.             {
  1688.                 // If overwrite mode was not specified by the caller, or it was but the existing
  1689.                 // destination file cannot be deleted (perhaps because it is a folder rather than
  1690.                 // a file), or it can be deleted but the source cannot be moved, indicate a failure.
  1691.                 // But by design, continue the operation.  The following relies heavily on
  1692.                 // short-circuit boolean evaluation order:
  1693.                 if (   !(bOverwrite && DeleteFile(szExpandedDest) && MoveFile(szTempPath, szExpandedDest))   )
  1694.                     ++failure_count; // At this stage, any of the above 3 being false is cause for failure.
  1695.                 //else everything succeeded, so nothing extra needs to be done.  In either case,
  1696.                 // continue on to the next file.
  1697.             }
  1698.         }
  1699.         else // The mode is "Copy" vs. "Move"
  1700.             if (!CopyFile(szTempPath, szExpandedDest, !bOverwrite)) // Force it to fail if bOverwrite==false.
  1701.                 ++failure_count;
  1702.     } while (FindNextFile(hSearch, &findData));
  1703.  
  1704.     FindClose(hSearch);
  1705.     return failure_count;
  1706. }
  1707.  
  1708.  
  1709.  
  1710. void Line::Util_ExpandFilenameWildcard(const char *szSource, const char *szDest, char *szExpandedDest)
  1711. {
  1712.     // copy one.two.three  *.txt     = one.two   .txt
  1713.     // copy one.two.three  *.*.txt   = one.two   .three  .txt
  1714.     // copy one.two.three  *.*.*.txt = one.two   .three  ..txt
  1715.     // copy one.two           test      = test
  1716.  
  1717.     char    szFileTemp[_MAX_PATH+1];
  1718.     char    szExtTemp[_MAX_PATH+1];
  1719.  
  1720.     char    szSrcFile[_MAX_PATH+1];
  1721.     char    szSrcExt[_MAX_PATH+1];
  1722.  
  1723.     char    szDestDrive[_MAX_PATH+1];
  1724.     char    szDestDir[_MAX_PATH+1];
  1725.     char    szDestFile[_MAX_PATH+1];
  1726.     char    szDestExt[_MAX_PATH+1];
  1727.  
  1728.     // If the destination doesn't include a wildcard, send it back vertabim
  1729.     if (strchr(szDest, '*') == NULL)
  1730.     {
  1731.         strcpy(szExpandedDest, szDest);
  1732.         return;
  1733.     }
  1734.  
  1735.     // Split source and dest into file and extension
  1736.     _splitpath( szSource, szDestDrive, szDestDir, szSrcFile, szSrcExt );
  1737.     _splitpath( szDest, szDestDrive, szDestDir, szDestFile, szDestExt );
  1738.  
  1739.     // Source and Dest ext will either be ".nnnn" or "" or ".*", remove the period
  1740.     if (szSrcExt[0] == '.')
  1741.         strcpy(szSrcExt, &szSrcExt[1]);
  1742.     if (szDestExt[0] == '.')
  1743.         strcpy(szDestExt, &szDestExt[1]);
  1744.  
  1745.     // Start of the destination with the drive and dir
  1746.     strcpy(szExpandedDest, szDestDrive);
  1747.     strcat(szExpandedDest, szDestDir);
  1748.  
  1749.     // Replace first * in the destext with the srcext, remove any other *
  1750.     Util_ExpandFilenameWildcardPart(szSrcExt, szDestExt, szExtTemp);
  1751.  
  1752.     // Replace first * in the destfile with the srcfile, remove any other *
  1753.     Util_ExpandFilenameWildcardPart(szSrcFile, szDestFile, szFileTemp);
  1754.  
  1755.     // Concat the filename and extension if req
  1756.     if (szExtTemp[0] != '\0')
  1757.     {
  1758.         strcat(szFileTemp, ".");
  1759.         strcat(szFileTemp, szExtTemp);    
  1760.     }
  1761.     else
  1762.     {
  1763.         // Dest extension was blank SOURCE MIGHT NOT HAVE BEEN!
  1764.         if (szSrcExt[0] != '\0')
  1765.         {
  1766.             strcat(szFileTemp, ".");
  1767.             strcat(szFileTemp, szSrcExt);    
  1768.         }
  1769.     }
  1770.  
  1771.     // Now add the drive and directory bit back onto the dest
  1772.     strcat(szExpandedDest, szFileTemp);
  1773.  
  1774. }
  1775.  
  1776.  
  1777.  
  1778. void Line::Util_ExpandFilenameWildcardPart(const char *szSource, const char *szDest, char *szExpandedDest)
  1779. {
  1780.     char    *lpTemp;
  1781.     int        i, j, k;
  1782.  
  1783.     // Replace first * in the dest with the src, remove any other *
  1784.     i = 0; j = 0; k = 0;
  1785.     lpTemp = (char *)strchr(szDest, '*');
  1786.     if (lpTemp != NULL)
  1787.     {
  1788.         // Contains at least one *, copy up to this point
  1789.         while(szDest[i] != '*')
  1790.             szExpandedDest[j++] = szDest[i++];
  1791.         // Skip the * and replace in the dest with the srcext
  1792.         while(szSource[k] != '\0')
  1793.             szExpandedDest[j++] = szSource[k++];
  1794.         // Skip any other *
  1795.         i++;
  1796.         while(szDest[i] != '\0')
  1797.         {
  1798.             if (szDest[i] == '*')
  1799.                 i++;
  1800.             else
  1801.                 szExpandedDest[j++] = szDest[i++];
  1802.         }
  1803.         szExpandedDest[j] = '\0';
  1804.     }
  1805.     else
  1806.     {
  1807.         // No wildcard, straight copy of destext
  1808.         strcpy(szExpandedDest, szDest);
  1809.     }
  1810. }
  1811.  
  1812.  
  1813.  
  1814. bool Line::Util_CreateDir(const char *szDirName) // Recursive directory creation function.
  1815. {
  1816.     DWORD    dwTemp;
  1817.     char    *szTemp = NULL;
  1818.     char    *psz_Loc = NULL;
  1819.     size_t  length;
  1820.  
  1821.     dwTemp = GetFileAttributes(szDirName);
  1822.  
  1823.     if (dwTemp == 0xffffffff) 
  1824.     {    // error getting attribute - what was the error?
  1825.         switch (GetLastError())
  1826.         {
  1827.         case ERROR_PATH_NOT_FOUND:
  1828.             // Create path
  1829.             length = strlen(szDirName);
  1830.             if (length > MAX_PATH) // Sanity check to reduce chance of stack overflow (since this function recursively calls self).
  1831.                 return false;
  1832.             szTemp = (char *)_alloca(length+1); // Faster, and also avoids need to delete it afterward.
  1833.             strcpy(szTemp, szDirName);
  1834.             psz_Loc = strrchr(szTemp, '\\');    /* find last \ */
  1835.             if (psz_Loc == NULL)                // not found
  1836.                 return false;
  1837.             else 
  1838.             {
  1839.                 *psz_Loc = '\0';                // remove \ and everything after
  1840.                 if (!Util_CreateDir(szTemp))
  1841.                     return false;
  1842.                 return CreateDirectory(szDirName, NULL) ? true : false;
  1843.             }
  1844.             // All paths above "return".
  1845.         case ERROR_FILE_NOT_FOUND:
  1846.             // Create directory
  1847.             return CreateDirectory(szDirName, NULL);
  1848.         // Otherwise, it's some unforeseen error, so fall through to the end, which reports failure.
  1849.         } // switch()
  1850.     }
  1851.     else // The specified name already exists as a file or directory.
  1852.         if (dwTemp & FILE_ATTRIBUTE_DIRECTORY) // Fixed for v1.0.36.01 (previously it used == vs &).
  1853.             return true;                            // Directory exists, yay!
  1854.         //else it exists, but it's a file! Not allowed, so fall through and report failure.
  1855.             
  1856.     return false;
  1857.  
  1858. }
  1859.  
  1860.  
  1861.  
  1862. bool Line::Util_DoesFileExist(const char *szFilename)  // Returns true if file or directory exists.
  1863. {
  1864.     if ( strchr(szFilename,'*')||strchr(szFilename,'?') )
  1865.     {
  1866.         WIN32_FIND_DATA    wfd;
  1867.         HANDLE            hFile;
  1868.  
  1869.         hFile = FindFirstFile(szFilename, &wfd);
  1870.  
  1871.         if ( hFile == INVALID_HANDLE_VALUE )
  1872.             return false;
  1873.  
  1874.         FindClose(hFile);
  1875.         return true;
  1876.     }
  1877.     else
  1878.     {
  1879.         DWORD dwTemp;
  1880.  
  1881.         dwTemp = GetFileAttributes(szFilename);
  1882.         if ( dwTemp != 0xffffffff )
  1883.             return true;
  1884.         else
  1885.             return false;
  1886.     }
  1887. }
  1888.  
  1889.  
  1890.  
  1891. bool Line::Util_IsDir(const char *szPath) // Returns true if the path is a directory
  1892. {
  1893.     DWORD dwTemp = GetFileAttributes(szPath);
  1894.     return dwTemp != 0xffffffff && (dwTemp & FILE_ATTRIBUTE_DIRECTORY);
  1895. }
  1896.  
  1897.  
  1898.  
  1899. void Line::Util_GetFullPathName(const char *szIn, char *szOut)
  1900. // Returns the full pathname and strips any trailing \s.  Assumes output is _MAX_PATH in size.
  1901. {
  1902.     char    *szFilePart;
  1903.     GetFullPathName(szIn, _MAX_PATH, szOut, &szFilePart);
  1904.     strip_trailing_backslash(szOut);
  1905. }
  1906.  
  1907.  
  1908.  
  1909. bool Line::Util_IsDifferentVolumes(const char *szPath1, const char *szPath2)
  1910. // Checks two paths to see if they are on the same volume.
  1911. {
  1912.     char            szP1Drive[_MAX_DRIVE+1];
  1913.     char            szP2Drive[_MAX_DRIVE+1];
  1914.  
  1915.     char            szDir[_MAX_DIR+1];
  1916.     char            szFile[_MAX_FNAME+1];
  1917.     char            szExt[_MAX_EXT+1];
  1918.     
  1919.     char            szP1[_MAX_PATH+1];    
  1920.     char            szP2[_MAX_PATH+1];
  1921.  
  1922.     // Get full pathnames
  1923.     Util_GetFullPathName(szPath1, szP1);
  1924.     Util_GetFullPathName(szPath2, szP2);
  1925.  
  1926.     // Split the target into bits
  1927.     _splitpath( szP1, szP1Drive, szDir, szFile, szExt );
  1928.     _splitpath( szP2, szP2Drive, szDir, szFile, szExt );
  1929.  
  1930.     if (szP1Drive[0] == '\0' || szP2Drive[0] == '\0')
  1931.         // One or both paths is a UNC - assume different volumes
  1932.         return true;
  1933.     else
  1934.         return stricmp(szP1Drive, szP2Drive);
  1935. }
  1936.  
  1937.  
  1938.  
  1939. bool Util_Shutdown(int nFlag)
  1940. // Shutdown or logoff the system.
  1941. // Returns false if the function could not get the rights to shutdown.
  1942. {
  1943. /* 
  1944. flags can be a combination of:
  1945. #define EWX_LOGOFF           0
  1946. #define EWX_SHUTDOWN         0x00000001
  1947. #define EWX_REBOOT           0x00000002
  1948. #define EWX_FORCE            0x00000004
  1949. #define EWX_POWEROFF         0x00000008 */
  1950.  
  1951.     HANDLE                hToken; 
  1952.     TOKEN_PRIVILEGES    tkp; 
  1953.  
  1954.     // If we are running NT/2k/XP, make sure we have rights to shutdown
  1955.     if (g_os.IsWinNT()) // NT/2k/XP/2003 and family
  1956.     {
  1957.         // Get a token for this process.
  1958.          if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
  1959.             return false;                        // Don't have the rights
  1960.  
  1961.         // Get the LUID for the shutdown privilege.
  1962.          LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); 
  1963.  
  1964.         tkp.PrivilegeCount = 1;  /* one privilege to set */
  1965.         tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
  1966.  
  1967.         // Get the shutdown privilege for this process.
  1968.          AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); 
  1969.  
  1970.         // Cannot test the return value of AdjustTokenPrivileges.
  1971.          if (GetLastError() != ERROR_SUCCESS) 
  1972.             return false;                        // Don't have the rights
  1973.     }
  1974.  
  1975.     // if we are forcing the issue, AND this is 95/98 terminate all windows first
  1976.     if ( g_os.IsWin9x() && (nFlag & EWX_FORCE) ) 
  1977.     {
  1978.         nFlag ^= EWX_FORCE;    // remove this flag - not valid in 95
  1979.         EnumWindows((WNDENUMPROC) Util_ShutdownHandler, 0);
  1980.     }
  1981.  
  1982.     // ExitWindows
  1983.     if (ExitWindowsEx(nFlag, 0))
  1984.         return true;
  1985.     else
  1986.         return false;
  1987.  
  1988. }
  1989.  
  1990.  
  1991.  
  1992. BOOL Util_ShutdownHandler(HWND hwnd, DWORD lParam)
  1993. {
  1994.     // if the window is me, don't terminate!
  1995.     if (hwnd != g_hWnd && hwnd != g_hWndSplash)
  1996.         Util_WinKill(hwnd);
  1997.  
  1998.     // Continue the enumeration.
  1999.     return TRUE;
  2000.  
  2001. }
  2002.  
  2003.  
  2004.  
  2005. void Util_WinKill(HWND hWnd)
  2006. {
  2007.     DWORD dwResult;
  2008.     // Use WM_CLOSE vs. SC_CLOSE in this case, since the target window is slightly more likely to
  2009.     // respond to that:
  2010.     if (!SendMessageTimeout(hWnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 500, &dwResult)) // Wait up to 500ms.
  2011.     {
  2012.         // Use more force - Mwuahaha
  2013.         DWORD pid = GetWindowThreadProcessId(hWnd, NULL);
  2014.         HANDLE hProcess = pid ? OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) : NULL;
  2015.         if (hProcess)
  2016.         {
  2017.             TerminateProcess(hProcess, 0);
  2018.             CloseHandle(hProcess);
  2019.         }
  2020.     }
  2021. }
  2022.  
  2023.  
  2024.  
  2025. void DoIncrementalMouseMove(int aX1, int aY1, int aX2, int aY2, int aSpeed)
  2026. // aX1 and aY1 are the starting coordinates, and "2" are the destination coordinates.
  2027. // Caller has ensured that aSpeed is in the range 0 to 100, inclusive.
  2028. {
  2029.     // AutoIt3: So, it's a more gradual speed that is needed :)
  2030.     int delta;
  2031.     #define INCR_MOUSE_MIN_SPEED 32
  2032.  
  2033.     while (aX1 != aX2 || aY1 != aY2)
  2034.     {
  2035.         if (aX1 < aX2)
  2036.         {
  2037.             delta = (aX2 - aX1) / aSpeed;
  2038.             if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  2039.                 delta = INCR_MOUSE_MIN_SPEED;
  2040.             if ((aX1 + delta) > aX2)
  2041.                 aX1 = aX2;
  2042.             else
  2043.                 aX1 += delta;
  2044.         } 
  2045.         else 
  2046.             if (aX1 > aX2)
  2047.             {
  2048.                 delta = (aX1 - aX2) / aSpeed;
  2049.                 if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  2050.                     delta = INCR_MOUSE_MIN_SPEED;
  2051.                 if ((aX1 - delta) < aX2)
  2052.                     aX1 = aX2;
  2053.                 else
  2054.                     aX1 -= delta;
  2055.             }
  2056.  
  2057.         if (aY1 < aY2)
  2058.         {
  2059.             delta = (aY2 - aY1) / aSpeed;
  2060.             if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  2061.                 delta = INCR_MOUSE_MIN_SPEED;
  2062.             if ((aY1 + delta) > aY2)
  2063.                 aY1 = aY2;
  2064.             else
  2065.                 aY1 += delta;
  2066.         } 
  2067.         else 
  2068.             if (aY1 > aY2)
  2069.             {
  2070.                 delta = (aY1 - aY2) / aSpeed;
  2071.                 if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  2072.                     delta = INCR_MOUSE_MIN_SPEED;
  2073.                 if ((aY1 - delta) < aY2)
  2074.                     aY1 = aY2;
  2075.                 else
  2076.                     aY1 -= delta;
  2077.             }
  2078.  
  2079.         MouseEvent(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, 0, aX1, aY1);
  2080.         DoMouseDelay();
  2081.         // Above: A delay is required for backward compatibility and because it's just how the incremental-move
  2082.         // feature was originally designed in AutoIt v3.  It may in fact improve reliability in some cases,
  2083.         // especially with the mouse_event() method vs. SendInput/Play.
  2084.     } // while()
  2085. }
  2086.  
  2087.  
  2088.  
  2089. ////////////////////
  2090. // PROCESS ROUTINES
  2091. ////////////////////
  2092.  
  2093. DWORD ProcessExist9x2000(char *aProcess, char *aProcessName)
  2094. {
  2095.     if (aProcessName) // Init this output variable in case of early return.
  2096.         *aProcessName = '\0';
  2097.  
  2098.     // We must dynamically load the function or program will probably not launch at all on NT4.
  2099.     typedef BOOL (WINAPI *PROCESSWALK)(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
  2100.     typedef HANDLE (WINAPI *CREATESNAPSHOT)(DWORD dwFlags, DWORD th32ProcessID);
  2101.  
  2102.     static CREATESNAPSHOT lpfnCreateToolhelp32Snapshot = (CREATESNAPSHOT)GetProcAddress(GetModuleHandle("kernel32"), "CreateToolhelp32Snapshot");
  2103.     static PROCESSWALK lpfnProcess32First = (PROCESSWALK)GetProcAddress(GetModuleHandle("kernel32"), "Process32First");
  2104.     static PROCESSWALK lpfnProcess32Next = (PROCESSWALK)GetProcAddress(GetModuleHandle("kernel32"), "Process32Next");
  2105.  
  2106.     if (!lpfnCreateToolhelp32Snapshot || !lpfnProcess32First || !lpfnProcess32Next)
  2107.         return 0;
  2108.  
  2109.     PROCESSENTRY32 proc;
  2110.     proc.dwSize = sizeof(proc);
  2111.     HANDLE snapshot = lpfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  2112.     lpfnProcess32First(snapshot, &proc);
  2113.  
  2114.     // Determine the PID if aProcess is a pure, non-negative integer (any negative number
  2115.     // is more likely to be the name of a process [with a leading dash], rather than the PID).
  2116.     DWORD specified_pid = IsPureNumeric(aProcess) ? ATOU(aProcess) : 0;
  2117.     char szDrive[_MAX_PATH+1], szDir[_MAX_PATH+1], szFile[_MAX_PATH+1], szExt[_MAX_PATH+1];
  2118.  
  2119.     while (lpfnProcess32Next(snapshot, &proc))
  2120.     {
  2121.         if (specified_pid && specified_pid == proc.th32ProcessID)
  2122.         {
  2123.             if (aProcessName) // Caller wanted process name also.
  2124.             {
  2125.                 // For consistency in results, use _splitpath() both here and below rather than
  2126.                 // something that just checks for a rightmost backslash.
  2127.                 _splitpath(proc.szExeFile, szDrive, szDir, aProcessName, szExt);
  2128.                 strcat(aProcessName, szExt);
  2129.             }
  2130.             CloseHandle(snapshot);
  2131.             return specified_pid;
  2132.         }
  2133.         // Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
  2134.         // also be a valid name?):
  2135.         // It seems that proc.szExeFile never contains a path, just the executable name.
  2136.         // But in case it ever does, ensure consistency by removing the path:
  2137.         _splitpath(proc.szExeFile, szDrive, szDir, szFile, szExt);
  2138.         strcat(szFile, szExt);
  2139.         if (!stricmp(szFile, aProcess)) // lstrcmpi() is not used: 1) avoids breaking exisitng scripts; 2) provides consistent behavior across multiple locales; 3) performance.
  2140.         {
  2141.             if (aProcessName) // Caller wanted process name also.
  2142.                 strcpy(aProcessName, szFile);
  2143.             CloseHandle(snapshot);
  2144.             return proc.th32ProcessID;
  2145.         }
  2146.     }
  2147.     CloseHandle(snapshot);
  2148.     return 0;  // Not found.
  2149. }
  2150.  
  2151.  
  2152.  
  2153. DWORD ProcessExistNT4(char *aProcess, char *aProcessName)
  2154. {
  2155.     if (aProcessName) // Init this output variable in case of early return.
  2156.         *aProcessName = '\0';
  2157.     //BOOL EnumProcesses(
  2158.     //  DWORD *lpidProcess,  // array of process identifiers
  2159.     //  DWORD cb,            // size of array
  2160.     //  DWORD *cbNeeded      // number of bytes returned
  2161.     //);
  2162.     typedef BOOL (WINAPI *MyEnumProcesses)(DWORD*, DWORD, DWORD*);
  2163.  
  2164.     //BOOL EnumProcessModules(
  2165.     //  HANDLE hProcess,      // handle to process
  2166.     //  HMODULE *lphModule,   // array of module handles
  2167.     //  DWORD cb,             // size of array
  2168.     //  LPDWORD lpcbNeeded    // number of bytes required
  2169.     //);
  2170.     typedef BOOL (WINAPI *MyEnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);
  2171.  
  2172.     //DWORD GetModuleBaseName(
  2173.     //  HANDLE hProcess,    // handle to process
  2174.     //  HMODULE hModule,    // handle to module
  2175.     //  LPTSTR lpBaseName,  // base name buffer
  2176.     //  DWORD nSize         // maximum characters to retrieve
  2177.     //);
  2178.     typedef DWORD (WINAPI *MyGetModuleBaseName)(HANDLE, HMODULE, LPTSTR, DWORD);
  2179.  
  2180.     // We must dynamically load the function or program will probably not launch at all on Win95.
  2181.     // Get a handle to the DLL module that contains EnumProcesses
  2182.     HINSTANCE hinstLib = LoadLibrary("psapi");
  2183.     if (!hinstLib)
  2184.         return 0;
  2185.  
  2186.     // Not static in this case, since address can change with each new load of the library:
  2187.       MyEnumProcesses lpfnEnumProcesses = (MyEnumProcesses)GetProcAddress(hinstLib, "EnumProcesses");
  2188.     MyEnumProcessModules lpfnEnumProcessModules = (MyEnumProcessModules)GetProcAddress(hinstLib, "EnumProcessModules");
  2189.     MyGetModuleBaseName lpfnGetModuleBaseName = (MyGetModuleBaseName)GetProcAddress(hinstLib, "GetModuleBaseNameA");
  2190.  
  2191.     DWORD idProcessArray[512];        // 512 processes max
  2192.     DWORD cbNeeded;                    // Bytes returned
  2193.     if (!lpfnEnumProcesses || !lpfnEnumProcessModules || !lpfnGetModuleBaseName
  2194.         || !lpfnEnumProcesses(idProcessArray, sizeof(idProcessArray), &cbNeeded))
  2195.     {
  2196.         FreeLibrary(hinstLib);
  2197.         return 0;
  2198.     }
  2199.  
  2200.     // Get the count of PIDs in the array
  2201.     DWORD cProcesses = cbNeeded / sizeof(DWORD);
  2202.     // Determine the PID if aProcess is a pure, non-negative integer (any negative number
  2203.     // is more likely to be the name of a process [with a leading dash], rather than the PID).
  2204.     DWORD specified_pid = IsPureNumeric(aProcess) ? ATOU(aProcess) : 0;
  2205.     char szDrive[_MAX_PATH+1], szDir[_MAX_PATH+1], szFile[_MAX_PATH+1], szExt[_MAX_PATH+1];
  2206.     char szProcessName[_MAX_PATH+1];
  2207.     HMODULE hMod;
  2208.     HANDLE hProcess;
  2209.  
  2210.     for (UINT i = 0; i < cProcesses; ++i)
  2211.     {
  2212.         if (specified_pid && specified_pid == idProcessArray[i])
  2213.         {
  2214.             if (aProcessName) // Caller wanted process name also.
  2215.             {
  2216.                 if (hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, idProcessArray[i])) // Assign
  2217.                 {
  2218.                     lpfnEnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded);
  2219.                     if (lpfnGetModuleBaseName(hProcess, hMod, szProcessName, _MAX_PATH))
  2220.                     {
  2221.                         // For consistency in results, use _splitpath() both here and below rather than
  2222.                         // something that just checks for a rightmost backslash.
  2223.                         _splitpath(szProcessName, szDrive, szDir, aProcessName, szExt);
  2224.                         strcat(aProcessName, szExt);
  2225.                     }
  2226.                     CloseHandle(hProcess);
  2227.                 }
  2228.             }
  2229.             FreeLibrary(hinstLib);
  2230.             return specified_pid;
  2231.         }
  2232.         // Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
  2233.         // also be a valid name?):
  2234.         if (hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, idProcessArray[i])) // Assign
  2235.         {
  2236.             lpfnEnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded);
  2237.             if (lpfnGetModuleBaseName(hProcess, hMod, szProcessName, _MAX_PATH))
  2238.             {
  2239.                 _splitpath(szProcessName, szDrive, szDir, szFile, szExt);
  2240.                 strcat(szFile, szExt);
  2241.                 if (!stricmp(szFile, aProcess)) // lstrcmpi() is not used: 1) avoids breaking exisitng scripts; 2) provides consistent behavior across multiple locales; 3) performance.
  2242.                 {
  2243.                     if (aProcessName) // Caller wanted process name also.
  2244.                         strcpy(aProcessName, szProcessName);
  2245.                     CloseHandle(hProcess);
  2246.                     FreeLibrary(hinstLib);
  2247.                     return idProcessArray[i];  // The PID.
  2248.                 }
  2249.             }
  2250.             CloseHandle(hProcess);
  2251.         }
  2252.     }
  2253.     FreeLibrary(hinstLib);
  2254.     return 0;  // Not found.
  2255. }
  2256.